ssh conn sort of working

This commit is contained in:
EthanShoeDev
2025-09-14 02:40:32 -04:00
parent 39027ee1a1
commit 26e206abf2
2 changed files with 43 additions and 37 deletions

View File

@@ -5,6 +5,13 @@ import * as SecureStore from 'expo-secure-store';
import * as z from 'zod'; import * as z from 'zod';
import { queryClient, type StrictOmit } from './utils'; import { queryClient, type StrictOmit } from './utils';
const shouldLog = false as boolean;
const log = (...args: Parameters<typeof console.log>) => {
if (shouldLog) {
console.log(...args);
}
};
function splitIntoChunks(data: string, chunkSize: number): string[] { function splitIntoChunks(data: string, chunkSize: number): string[] {
const chunks: string[] = []; const chunks: string[] = [];
for (let i = 0; i < data.length; i += chunkSize) { for (let i = 0; i < data.length; i += chunkSize) {
@@ -67,9 +74,9 @@ function makeBetterSecureStore<
const rawRootManifestString = const rawRootManifestString =
await SecureStore.getItemAsync(rootManifestKey); await SecureStore.getItemAsync(rootManifestKey);
console.log('DEBUG rawRootManifestString', rawRootManifestString); log('DEBUG rawRootManifestString', rawRootManifestString);
console.log( log(
`Root manifest for ${rootManifestKey} is ${rawRootManifestString?.length ?? 0} bytes`, `Root manifest for ${rootManifestKey} is ${rawRootManifestString?.length ?? 0} bytes`,
); );
const unsafedRootManifest = rawRootManifestString const unsafedRootManifest = rawRootManifestString
@@ -87,7 +94,7 @@ function makeBetterSecureStore<
); );
if (!rawManifestChunkString) if (!rawManifestChunkString)
throw new Error('Manifest chunk not found'); throw new Error('Manifest chunk not found');
console.log( log(
`Manifest chunk for ${manifestChunkKeyString} is ${rawManifestChunkString?.length} bytes`, `Manifest chunk for ${manifestChunkKeyString} is ${rawManifestChunkString?.length} bytes`,
); );
const unsafedManifestChunk = JSON.parse(rawManifestChunkString); const unsafedManifestChunk = JSON.parse(rawManifestChunkString);
@@ -111,7 +118,7 @@ function makeBetterSecureStore<
Array.from({ length: manifestEntry.chunkCount }, async (_, chunkIdx) => { Array.from({ length: manifestEntry.chunkCount }, async (_, chunkIdx) => {
const entryKeyString = entryKey(manifestEntry.id, chunkIdx); const entryKeyString = entryKey(manifestEntry.id, chunkIdx);
const rawEntryChunk = await SecureStore.getItemAsync(entryKeyString); const rawEntryChunk = await SecureStore.getItemAsync(entryKeyString);
console.log( log(
`Entry chunk for ${entryKeyString} is ${rawEntryChunk?.length} bytes`, `Entry chunk for ${entryKeyString} is ${rawEntryChunk?.length} bytes`,
); );
if (!rawEntryChunk) throw new Error('Entry chunk not found'); if (!rawEntryChunk) throw new Error('Entry chunk not found');
@@ -197,10 +204,7 @@ function makeBetterSecureStore<
(mChunk) => mChunk.manifestChunk.entries.length === 0, (mChunk) => mChunk.manifestChunk.entries.length === 0,
); );
if (emptyManifestChunks.length > 0) { if (emptyManifestChunks.length > 0) {
console.log( log('DEBUG: removing empty manifest chunks', emptyManifestChunks.length);
'DEBUG: removing empty manifest chunks',
emptyManifestChunks.length,
);
manifest.rootManifest.manifestChunksIds = manifest.rootManifest.manifestChunksIds =
manifest.rootManifest.manifestChunksIds.filter( manifest.rootManifest.manifestChunksIds.filter(
(mChunkId) => (mChunkId) =>
@@ -228,7 +232,7 @@ function makeBetterSecureStore<
value: string; value: string;
}) { }) {
await deleteEntry(params.id).catch(() => { await deleteEntry(params.id).catch(() => {
console.log(`Entry ${params.id} not found, creating new one`); log(`Entry ${params.id} not found, creating new one`);
}); });
const valueChunks = splitIntoChunks(params.value, sizeLimit); const valueChunks = splitIntoChunks(params.value, sizeLimit);
@@ -245,10 +249,7 @@ function makeBetterSecureStore<
const existingManifestChunkWithRoom = manifest.manifestChunks.find( const existingManifestChunkWithRoom = manifest.manifestChunks.find(
(mChunk) => sizeLimit > mChunk.manifestChunkSize + newManifestEntrySize, (mChunk) => sizeLimit > mChunk.manifestChunkSize + newManifestEntrySize,
); );
console.log( log('DEBUG existingManifestChunkWithRoom', existingManifestChunkWithRoom);
'DEBUG existingManifestChunkWithRoom',
existingManifestChunkWithRoom,
);
const manifestChunkWithRoom = const manifestChunkWithRoom =
existingManifestChunkWithRoom ?? existingManifestChunkWithRoom ??
(await (async () => { (await (async () => {
@@ -260,9 +261,7 @@ function makeBetterSecureStore<
manifestChunkId: Crypto.randomUUID(), manifestChunkId: Crypto.randomUUID(),
manifestChunkSize: 0, manifestChunkSize: 0,
} satisfies NonNullable<(typeof manifest.manifestChunks)[number]>; } satisfies NonNullable<(typeof manifest.manifestChunks)[number]>;
console.log( log(`Adding new manifest chunk ${newManifestChunk.manifestChunkId}`);
`Adding new manifest chunk ${newManifestChunk.manifestChunkId}`,
);
manifest.rootManifest.manifestChunksIds.push( manifest.rootManifest.manifestChunksIds.push(
newManifestChunk.manifestChunkId, newManifestChunk.manifestChunkId,
); );
@@ -270,7 +269,7 @@ function makeBetterSecureStore<
rootManifestKey, rootManifestKey,
JSON.stringify(manifest.rootManifest), JSON.stringify(manifest.rootManifest),
); );
console.log('DEBUG: newRootManifest', manifest.rootManifest); log('DEBUG: newRootManifest', manifest.rootManifest);
return newManifestChunk; return newManifestChunk;
})()); })());
@@ -283,14 +282,14 @@ function makeBetterSecureStore<
manifestChunkKeyString, manifestChunkKeyString,
JSON.stringify(manifestChunkWithRoom.manifestChunk), JSON.stringify(manifestChunkWithRoom.manifestChunk),
).then(() => { ).then(() => {
console.log( log(
`Set manifest chunk for ${manifestChunkKeyString} to ${JSON.stringify(manifestChunkWithRoom.manifestChunk).length} bytes`, `Set manifest chunk for ${manifestChunkKeyString} to ${JSON.stringify(manifestChunkWithRoom.manifestChunk).length} bytes`,
); );
}), }),
...valueChunks.map(async (vChunk, chunkIdx) => { ...valueChunks.map(async (vChunk, chunkIdx) => {
const entryKeyString = entryKey(newManifestEntry.id, chunkIdx); const entryKeyString = entryKey(newManifestEntry.id, chunkIdx);
await SecureStore.setItemAsync(entryKeyString, vChunk); await SecureStore.setItemAsync(entryKeyString, vChunk);
console.log( log(
`Set entry chunk for ${entryKeyString} ${chunkIdx} to ${vChunk.length} bytes`, `Set entry chunk for ${entryKeyString} ${chunkIdx} to ${vChunk.length} bytes`,
); );
}), }),
@@ -328,7 +327,7 @@ async function upsertPrivateKey(params: {
metadata: StrictOmit<KeyMetadata, 'createdAtMs'>; metadata: StrictOmit<KeyMetadata, 'createdAtMs'>;
value: string; value: string;
}) { }) {
console.log(`Upserting private key ${params.keyId}`); log(`Upserting private key ${params.keyId}`);
// Preserve createdAtMs if the entry already exists // Preserve createdAtMs if the entry already exists
const existing = await betterKeyStorage const existing = await betterKeyStorage
.getEntry(params.keyId) .getEntry(params.keyId)
@@ -344,7 +343,7 @@ async function upsertPrivateKey(params: {
}, },
value: params.value, value: params.value,
}); });
console.log('DEBUG: invalidating key query'); log('DEBUG: invalidating key query');
await queryClient.invalidateQueries({ queryKey: [keyQueryKey] }); await queryClient.invalidateQueries({ queryKey: [keyQueryKey] });
} }
@@ -359,7 +358,7 @@ const listKeysQueryOptions = queryOptions({
queryKey: [keyQueryKey], queryKey: [keyQueryKey],
queryFn: async () => { queryFn: async () => {
const results = await betterKeyStorage.listEntriesWithValues(); const results = await betterKeyStorage.listEntriesWithValues();
console.log(`Listed ${results.length} private keys`); log(`Listed ${results.length} private keys`);
return results; return results;
}, },
}); });
@@ -412,7 +411,7 @@ async function upsertConnection(params: {
}, },
value: JSON.stringify(params.details), value: JSON.stringify(params.details),
}); });
console.log('DEBUG: invalidating connection query'); log('DEBUG: invalidating connection query');
await queryClient.invalidateQueries({ queryKey: [connectionQueryKey] }); await queryClient.invalidateQueries({ queryKey: [connectionQueryKey] });
return params.details; return params.details;
} }
@@ -443,7 +442,7 @@ async function generateKeyPair(params: {
keySize?: number; keySize?: number;
comment?: string; comment?: string;
}) { }) {
console.log('DEBUG: generating key pair', params); log('DEBUG: generating key pair', params);
const keyPair = await RnRussh.generateKeyPair( const keyPair = await RnRussh.generateKeyPair(
'ed25519', 'ed25519',
// params.keySize, // params.keySize,

View File

@@ -53,7 +53,6 @@ export type StartShellOptions = {
onStatusChange?: (status: SshConnectionStatus) => void; onStatusChange?: (status: SshConnectionStatus) => void;
abortSignal?: AbortSignal; abortSignal?: AbortSignal;
} }
async function connect(options: ConnectOptions) { async function connect(options: ConnectOptions) {
const security = const security =
options.security.type === 'password' options.security.type === 'password'
@@ -79,20 +78,28 @@ async function connect(options: ConnectOptions) {
} }
: undefined : undefined
); );
const originalStartShell = sshConnectionInterface.startShell; // Wrap startShell in-place to preserve the UniFFI object's internal pointer.
return { // Spreading into a new object would drop the hidden native pointer and cause
...sshConnectionInterface, // "Raw pointer value was null" when methods access `this`.
startShell: (params: StartShellOptions) => { const originalStartShell = sshConnectionInterface.startShell.bind(sshConnectionInterface);
return originalStartShell({ const betterStartShell = async (params: StartShellOptions) => {
return originalStartShell(
{
pty: ptyTypeLiteralToEnum[params.pty], pty: ptyTypeLiteralToEnum[params.pty],
onStatusChange: params.onStatusChange ? { onStatusChange: params.onStatusChange
onChange: (statusEnum) => { ? {
params.onStatusChange?.(sshConnStatusEnumToLiteral[statusEnum]!); onChange: (statusEnum) => {
}, params.onStatusChange?.(sshConnStatusEnumToLiteral[statusEnum]!);
} : undefined, },
}, params.abortSignal ? { signal: params.abortSignal } : undefined); }
} : undefined,
},
params.abortSignal ? { signal: params.abortSignal } : undefined,
);
} }
type BetterStartShellFn = typeof betterStartShell;
(sshConnectionInterface as any).startShell = betterStartShell
return sshConnectionInterface as GeneratedRussh.SshConnectionInterface & { startShell: BetterStartShellFn };
} }
export type SshConnection = Awaited<ReturnType<typeof connect>>; export type SshConnection = Awaited<ReturnType<typeof connect>>;