mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-11 14:22:51 +00:00
some changes
This commit is contained in:
@@ -32,7 +32,7 @@ export default function Index() {
|
|||||||
const connectionForm = useAppForm({
|
const connectionForm = useAppForm({
|
||||||
// https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values
|
// https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values
|
||||||
defaultValues: preferredStoredConnection
|
defaultValues: preferredStoredConnection
|
||||||
? preferredStoredConnection.details
|
? preferredStoredConnection.value
|
||||||
: defaultValues,
|
: defaultValues,
|
||||||
validators: {
|
validators: {
|
||||||
onChange: connectionDetailsSchema,
|
onChange: connectionDetailsSchema,
|
||||||
@@ -48,9 +48,10 @@ export default function Index() {
|
|||||||
value.security.password,
|
value.security.password,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const privateKey = await secretsManager.keys.utils.getPrivateKey(
|
const privateKey =
|
||||||
value.security.keyId,
|
await secretsManager.keys.utils.betterKeyStorage.getEntry(
|
||||||
);
|
value.security.keyId,
|
||||||
|
);
|
||||||
return await SSHClient.connectWithKey(
|
return await SSHClient.connectWithKey(
|
||||||
value.host,
|
value.host,
|
||||||
value.port,
|
value.port,
|
||||||
@@ -242,7 +243,7 @@ const KeyPairSection = withFieldGroup({
|
|||||||
type: 'rsa',
|
type: 'rsa',
|
||||||
keySize: 4096,
|
keySize: 4096,
|
||||||
});
|
});
|
||||||
await secretsManager.keys.utils.savePrivateKey({
|
await secretsManager.keys.utils.upsertPrivateKey({
|
||||||
keyId: 'default',
|
keyId: 'default',
|
||||||
privateKey: newKeyPair.privateKey,
|
privateKey: newKeyPair.privateKey,
|
||||||
priority: 0,
|
priority: 0,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import * as SecureStore from 'expo-secure-store';
|
|||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
import { queryClient } from './utils';
|
import { queryClient } from './utils';
|
||||||
|
|
||||||
// Utility functions for chunking large data
|
|
||||||
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) {
|
||||||
@@ -20,8 +19,12 @@ function splitIntoChunks(data: string, chunkSize: number): string[] {
|
|||||||
*
|
*
|
||||||
* We can bypass both of those by using manifest entries and chunking.
|
* We can bypass both of those by using manifest entries and chunking.
|
||||||
*/
|
*/
|
||||||
function makeBetterSecureStore<T extends object = object>(params: {
|
function makeBetterSecureStore<
|
||||||
|
T extends object = object,
|
||||||
|
Value = string,
|
||||||
|
>(params: {
|
||||||
storagePrefix: string;
|
storagePrefix: string;
|
||||||
|
parseValue: (value: string) => Value;
|
||||||
extraManifestFieldsSchema?: z.ZodType<T>;
|
extraManifestFieldsSchema?: z.ZodType<T>;
|
||||||
}) {
|
}) {
|
||||||
// const sizeLimit = 2048;
|
// const sizeLimit = 2048;
|
||||||
@@ -97,6 +100,22 @@ function makeBetterSecureStore<T extends object = object>(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function _getEntryValueFromManifestEntry(manifestEntry: Entry) {
|
||||||
|
const rawEntryChunks = await Promise.all(
|
||||||
|
Array.from({ length: manifestEntry.chunkCount }, async (_, chunkIdx) => {
|
||||||
|
const rawEntryChunk = await SecureStore.getItemAsync(
|
||||||
|
entryKey(manifestEntry.id, chunkIdx),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Entry chunk for ${params.storagePrefix} ${manifestEntry.id} ${chunkIdx} is ${rawEntryChunk?.length} bytes`,
|
||||||
|
);
|
||||||
|
return rawEntryChunk;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const entry = rawEntryChunks.join('');
|
||||||
|
return params.parseValue(entry);
|
||||||
|
}
|
||||||
|
|
||||||
async function getEntry(id: string) {
|
async function getEntry(id: string) {
|
||||||
const manifest = await getManifest();
|
const manifest = await getManifest();
|
||||||
const manifestEntry = manifest.manifestChunks.reduce<Entry | undefined>(
|
const manifestEntry = manifest.manifestChunks.reduce<Entry | undefined>(
|
||||||
@@ -106,26 +125,27 @@ function makeBetterSecureStore<T extends object = object>(params: {
|
|||||||
);
|
);
|
||||||
if (!manifestEntry) throw new Error('Entry not found');
|
if (!manifestEntry) throw new Error('Entry not found');
|
||||||
|
|
||||||
const rawEntryChunks = await Promise.all(
|
return _getEntryValueFromManifestEntry(manifestEntry);
|
||||||
Array.from({ length: manifestEntry.chunkCount }, async (_, chunkIdx) => {
|
|
||||||
const rawEntryChunk = await SecureStore.getItemAsync(
|
|
||||||
entryKey(id, chunkIdx),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`Entry chunk for ${params.storagePrefix} ${id} ${chunkIdx} is ${rawEntryChunk?.length} bytes`,
|
|
||||||
);
|
|
||||||
return rawEntryChunk;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const entry = rawEntryChunks.join('');
|
|
||||||
return entry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listEntries() {
|
async function listEntries() {
|
||||||
const manifest = await getManifest();
|
const manifest = await getManifest();
|
||||||
return manifest.manifestChunks.flatMap(
|
const manifestEntries = manifest.manifestChunks.flatMap(
|
||||||
(mChunk) => mChunk.manifestChunk.entries,
|
(mChunk) => mChunk.manifestChunk.entries,
|
||||||
);
|
);
|
||||||
|
return manifestEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listEntriesWithValues() {
|
||||||
|
const manifestEntries = await listEntries();
|
||||||
|
return await Promise.all(
|
||||||
|
manifestEntries.map(async (entry) => {
|
||||||
|
return {
|
||||||
|
...entry,
|
||||||
|
value: await _getEntryValueFromManifestEntry(entry),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteEntry(id: string) {
|
async function deleteEntry(id: string) {
|
||||||
@@ -224,6 +244,7 @@ function makeBetterSecureStore<T extends object = object>(params: {
|
|||||||
getManifest,
|
getManifest,
|
||||||
getEntry,
|
getEntry,
|
||||||
listEntries,
|
listEntries,
|
||||||
|
listEntriesWithValues,
|
||||||
upsertEntry,
|
upsertEntry,
|
||||||
deleteEntry,
|
deleteEntry,
|
||||||
};
|
};
|
||||||
@@ -235,18 +256,10 @@ const betterKeyStorage = makeBetterSecureStore({
|
|||||||
priority: z.number(),
|
priority: z.number(),
|
||||||
createdAtMs: z.int(),
|
createdAtMs: z.int(),
|
||||||
}),
|
}),
|
||||||
|
parseValue: (value) => value,
|
||||||
});
|
});
|
||||||
|
|
||||||
const betterConnectionStorage = makeBetterSecureStore({
|
async function upsertPrivateKey(params: {
|
||||||
storagePrefix: 'connection_',
|
|
||||||
extraManifestFieldsSchema: z.object({
|
|
||||||
priority: z.number(),
|
|
||||||
createdAtMs: z.int(),
|
|
||||||
modifiedAtMs: z.int(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
async function savePrivateKey(params: {
|
|
||||||
keyId: string;
|
keyId: string;
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
priority: number;
|
priority: number;
|
||||||
@@ -262,15 +275,24 @@ async function savePrivateKey(params: {
|
|||||||
await queryClient.invalidateQueries({ queryKey: [keyQueryKey] });
|
await queryClient.invalidateQueries({ queryKey: [keyQueryKey] });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPrivateKey(keyId: string) {
|
|
||||||
return await betterKeyStorage.getEntry(keyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deletePrivateKey(keyId: string) {
|
async function deletePrivateKey(keyId: string) {
|
||||||
await betterKeyStorage.deleteEntry(keyId);
|
await betterKeyStorage.deleteEntry(keyId);
|
||||||
await queryClient.invalidateQueries({ queryKey: [keyQueryKey] });
|
await queryClient.invalidateQueries({ queryKey: [keyQueryKey] });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const keyQueryKey = 'keys';
|
||||||
|
|
||||||
|
const listKeysQueryOptions = queryOptions({
|
||||||
|
queryKey: [keyQueryKey],
|
||||||
|
queryFn: async () => await betterKeyStorage.listEntries(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const getKeyQueryOptions = (keyId: string) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: [keyQueryKey, keyId],
|
||||||
|
queryFn: () => betterKeyStorage.getEntry(keyId),
|
||||||
|
});
|
||||||
|
|
||||||
export const connectionDetailsSchema = z.object({
|
export const connectionDetailsSchema = z.object({
|
||||||
host: z.string().min(1),
|
host: z.string().min(1),
|
||||||
port: z.number().min(1),
|
port: z.number().min(1),
|
||||||
@@ -287,6 +309,16 @@ export const connectionDetailsSchema = z.object({
|
|||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const betterConnectionStorage = makeBetterSecureStore({
|
||||||
|
storagePrefix: 'connection_',
|
||||||
|
extraManifestFieldsSchema: z.object({
|
||||||
|
priority: z.number(),
|
||||||
|
createdAtMs: z.int(),
|
||||||
|
modifiedAtMs: z.int(),
|
||||||
|
}),
|
||||||
|
parseValue: (value) => connectionDetailsSchema.parse(JSON.parse(value)),
|
||||||
|
});
|
||||||
|
|
||||||
export type ConnectionDetails = z.infer<typeof connectionDetailsSchema>;
|
export type ConnectionDetails = z.infer<typeof connectionDetailsSchema>;
|
||||||
|
|
||||||
async function upsertConnection(params: {
|
async function upsertConnection(params: {
|
||||||
@@ -312,43 +344,19 @@ async function deleteConnection(id: string) {
|
|||||||
await queryClient.invalidateQueries({ queryKey: [connectionQueryKey] });
|
await queryClient.invalidateQueries({ queryKey: [connectionQueryKey] });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getConnection(id: string) {
|
|
||||||
const connDetailsString = await betterConnectionStorage.getEntry(id);
|
|
||||||
return connectionDetailsSchema.parse(JSON.parse(connDetailsString));
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectionQueryKey = 'connections';
|
const connectionQueryKey = 'connections';
|
||||||
|
|
||||||
const listConnectionsQueryOptions = queryOptions({
|
const listConnectionsQueryOptions = queryOptions({
|
||||||
queryKey: [connectionQueryKey],
|
queryKey: [connectionQueryKey],
|
||||||
queryFn: async () => {
|
queryFn: () => betterConnectionStorage.listEntriesWithValues(),
|
||||||
const connManifests = await betterConnectionStorage.listEntries();
|
|
||||||
const results = await Promise.all(
|
|
||||||
connManifests.map(async (connManifest) => {
|
|
||||||
const details = await getConnection(connManifest.id);
|
|
||||||
return {
|
|
||||||
details,
|
|
||||||
id: connManifest.id,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return results;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getConnectionQueryOptions = (id: string) =>
|
const getConnectionQueryOptions = (id: string) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: [connectionQueryKey, id],
|
queryKey: [connectionQueryKey, id],
|
||||||
queryFn: () => getConnection(id),
|
queryFn: () => betterConnectionStorage.getEntry(id),
|
||||||
});
|
});
|
||||||
|
|
||||||
const keyQueryKey = 'keys';
|
|
||||||
|
|
||||||
const listKeysQueryOptions = queryOptions({
|
|
||||||
queryKey: [keyQueryKey],
|
|
||||||
queryFn: async () => await betterKeyStorage.listEntries(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://github.com/dylankenneally/react-native-ssh-sftp/blob/ea55436d8d40378a8f9dabb95b463739ffb219fa/android/src/main/java/me/keeex/rnssh/RNSshClientModule.java#L101-L119
|
// https://github.com/dylankenneally/react-native-ssh-sftp/blob/ea55436d8d40378a8f9dabb95b463739ffb219fa/android/src/main/java/me/keeex/rnssh/RNSshClientModule.java#L101-L119
|
||||||
export type SshPrivateKeyType = 'dsa' | 'rsa' | 'ecdsa' | 'ed25519' | 'ed448';
|
export type SshPrivateKeyType = 'dsa' | 'rsa' | 'ecdsa' | 'ed25519' | 'ed448';
|
||||||
async function generateKeyPair(params: {
|
async function generateKeyPair(params: {
|
||||||
@@ -369,18 +377,19 @@ async function generateKeyPair(params: {
|
|||||||
export const secretsManager = {
|
export const secretsManager = {
|
||||||
keys: {
|
keys: {
|
||||||
utils: {
|
utils: {
|
||||||
listPrivateKeys: () => betterKeyStorage.listEntries(),
|
betterKeyStorage,
|
||||||
savePrivateKey,
|
upsertPrivateKey,
|
||||||
getPrivateKey,
|
|
||||||
deletePrivateKey,
|
deletePrivateKey,
|
||||||
generateKeyPair,
|
generateKeyPair,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
list: listKeysQueryOptions,
|
list: listKeysQueryOptions,
|
||||||
|
get: getKeyQueryOptions,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
connections: {
|
connections: {
|
||||||
utils: {
|
utils: {
|
||||||
|
betterConnectionStorage,
|
||||||
upsertConnection,
|
upsertConnection,
|
||||||
deleteConnection,
|
deleteConnection,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,3 +5,5 @@
|
|||||||
https://github.com/software-mansion/react-native-executorch
|
https://github.com/software-mansion/react-native-executorch
|
||||||
https://docs.expo.dev/guides/keyboard-handling/
|
https://docs.expo.dev/guides/keyboard-handling/
|
||||||
https://kirillzyusko.github.io/react-native-keyboard-controller/
|
https://kirillzyusko.github.io/react-native-keyboard-controller/
|
||||||
|
|
||||||
|
https://reactnativereusables.com/
|
||||||
|
|||||||
Reference in New Issue
Block a user