Skip to content

Commit

Permalink
feat: start service worker with embedded sqlite db w/ wasm, start mes…
Browse files Browse the repository at this point in the history
…sage processing
  • Loading branch information
dallen4 committed Jan 28, 2025
1 parent 4f321a8 commit ffc3084
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 1 deletion.
22 changes: 22 additions & 0 deletions web/lib/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createClient } from '@libsql/client-wasm';
import { initDBConfig } from '../../shared/db/init';
import { CloudVaultConfig } from '../../shared/types/config';
import { drizzle } from 'drizzle-orm/libsql/wasm';

export const initDBClient = async (
path: string,
encryptionKey: string,
cloudConfig?: CloudVaultConfig,
) => {
const [clientConfig, drizzleConfig] = initDBConfig(
path,
encryptionKey,
cloudConfig,
);

const client = drizzle(createClient(clientConfig), drizzleConfig);

await client.$client.sync();

return client;
};
6 changes: 5 additions & 1 deletion web/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ const withTM = nextTranspileModules(['shared']);
import { withSentryConfig } from '@sentry/nextjs';

import nextPwa from 'next-pwa';
const withPWA = nextPwa({ dest: '/public' });

const withPWA = nextPwa({
dest: '/public',
customWorkerDir: 'scripts/service-worker',
});

const nonce = randomBytes(8).toString('base64');

Expand Down
124 changes: 124 additions & 0 deletions web/scripts/service-worker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/// <reference lib="webworker" />
import { initConfig } from '../../../shared/lib/vault';
import {
CONFIG_FILE_NAME,
DEFAULT_VAULT_NAME,
} from '../../../shared/lib/constants';
import type { DeadropConfig } from '../../../shared/types/config';
import { createSecretsHelpers } from '../../../shared/db/secrets';
import { initDBClient } from '../../lib/db';
import {
DeadropMessage,
DeadropServiceWorkerMessage,
} from 'types/worker';

const sw = self as unknown as ServiceWorkerGlobalScope;

const randomBase64 = () => {
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
return btoa(String.fromCharCode(...randomBytes));
};

async function processMessage(
message: DeadropMessage,
): Promise<DeadropMessage> {
const directoryHandler = await navigator.storage.getDirectory();
const fileHandler = await directoryHandler.getFileHandle(
CONFIG_FILE_NAME,
{ create: true },
);

let writeableStream: FileSystemWritableFileStream | undefined;

const writeConfig = async (updatedConfig: DeadropConfig) => {
writeableStream = await fileHandler.createWritable();
await writeableStream.write(JSON.stringify(updatedConfig));
};

let config: DeadropConfig;

try {
const configFile = await fileHandler.getFile();
const configString = await configFile.text();

config = JSON.parse(configString);
} catch (err) {
// if not found, create config
config = await initConfig(DEFAULT_VAULT_NAME, randomBase64());

await writeConfig(config);
}

if (message.type === 'get_config')
return { type: 'config', payload: config };

let response: DeadropMessage = {
type: 'notification',
payload: {
variant: 'success',
message: '',
},
};

if (message.type === 'set_config') {
await writeConfig(message.payload);

response.payload.message = 'deadrop config updated!';
} else if (message.type.includes('secret')) {
const activeVault = config.vaults[config.active_vault.name];

const db = await initDBClient(
activeVault.location,
activeVault.key,
activeVault.cloud,
);

const { addSecrets, updateSecret, getSecret, getAllSecrets } =
createSecretsHelpers(activeVault, db);

if (message.type === 'add_secret') {
await addSecrets([message.payload]);

response.payload.message = 'secret added to vault!';
} else if (message.type === 'update_secret') {
await updateSecret(message.payload);

response.payload.message = 'secret updated in vault!';
} else if (message.type === 'get_secret') {
const { name, environment } = message.payload;

const secretValue = await getSecret(name, environment);

response = {
type: 'secret',
payload: {
name,
value: secretValue,
environment,
},
};
} else if (message.type === 'get_secrets') {
const { environment } = message.payload;

const secrets = await getAllSecrets(environment);

response = {
type: 'all_secrets',
payload: [],
};
}
}

if (writeableStream) await writeableStream.close();

return response;
}

sw.addEventListener(
'message',
async (msg: DeadropServiceWorkerMessage) => {
const response = await processMessage(msg.data);

if (response) msg.ports[0].postMessage(response);
},
);
61 changes: 61 additions & 0 deletions web/types/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Secret } from '@shared/db/schema';
import { DeadropConfig } from '@shared/types/config';

type ConfigMessage =
| {
type: 'get_config';
payload: undefined;
}
| {
type: 'config' | 'set_config';
payload: DeadropConfig;
};

type VaultMessage =
| {
type: 'add_secret' | 'update_secret';
payload: {
name: string;
value: string;
environment: string;
};
}
| {
type: 'get_secret' | 'delete_secret';
payload: {
name: string;
environment: string;
};
}
| {
type: 'get_secrets';
payload: {
environment: string;
};
}
| {
type: 'secret';
payload: Secret;
}
| {
type: 'all_secrets';
payload: Secret[];
};

export type DeadropMessage =
| ConfigMessage
| VaultMessage
| {
type: 'notification';
payload: {
variant: 'success' | 'error';
message: string;
};
};

export type DeadropServiceWorkerMessage = Omit<
ExtendableMessageEvent,
'data'
> & {
data: DeadropMessage;
};

0 comments on commit ffc3084

Please sign in to comment.