Skip to content

Commit

Permalink
feat: SeedClient implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
y9san9 committed Feb 7, 2025
1 parent 687782c commit 02e7e52
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 177 deletions.
6 changes: 3 additions & 3 deletions src/sdk/crypto/string-to-base64.ts → src/crypto/base-64.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export function arrayBufferToBase64(buffer: ArrayBuffer) {
let binary = '';
let bytes = new Uint8Array(buffer);
let len = bytes.byteLength;
let binary = "";
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
Expand Down
Empty file added src/crypto/subtle.ts
Empty file.
71 changes: 69 additions & 2 deletions src/sdk-v2/seed-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@ import { createSeedClient, SeedClient } from "./seed-client";
import { randomAESKey } from "@/sdk/crypto/subtle-crypto";

Check failure on line 3 in src/sdk-v2/seed-client.test.ts

View workflow job for this annotation

GitHub Actions / Build

Cannot find module '@/sdk/crypto/subtle-crypto' or its corresponding type declarations.
import { encryptContent } from "@/sdk/crypto/encrypt-content";

test("seed-reconnect", async () => {
const client = connectClient();
await new Promise(resolve => setTimeout(resolve, 4_000));
});

test("seed-subscribe", async () => {
const client = connectClient();
client.subscribe(
"wss://meetacy.app/seed-go",
{
queueId: await randomAESKey(),
nonce: 0,
},
);
await new Promise(resolve => setTimeout(resolve, 4_000));
});

test("seed-client-send", async () => {
const client = connectClient();

Expand All @@ -13,7 +30,11 @@ test("seed-client-send", async () => {
key: await randomAESKey(),
});

const result = await client.send(
await new Promise(resolve => setTimeout(resolve, 2_000));

console.log("After timeout");

const goResult = await client.send(
"wss://meetacy.app/seed-go",
{
nonce: 0,
Expand All @@ -24,7 +45,53 @@ test("seed-client-send", async () => {
},
);

expect(result, "Message is not sent").toBe(true);
const ktResult = await client.send(
"wss://meetacy.app/seed-kt",
{
nonce: 0,
signature,
content,
contentIV,
queueId: chatId,
},
);

expect(goResult, "Message is not sent").toBe(true);
expect(ktResult, "Message is not sent").toBe(true);
});

test("subscribe-send-combination", async () => {
const client = connectClient();

const chatId = await randomAESKey();

const { content, contentIV, signature } = await encryptContent({
content: "Random Stuff in Here",
key: await randomAESKey(),
});


client.subscribe("wss://meetacy.app/seed-kt", {
queueId: chatId, nonce: 0,
});

const promise = new Promise<void>(resolve => {
const cancel = client.events.subscribe(event => {
if (event.type !== "new") return;
resolve();
cancel();
});
});

await client.send("wss://meetacy.app/seed-kt", {
nonce: 0,
signature,
content,
contentIV,
queueId: chatId,
});

await promise;
});

function connectClient(): SeedClient {
Expand Down
68 changes: 65 additions & 3 deletions src/sdk-v2/seed-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ export type SeedClientEvent = {
} | {
type: "new";
url: string;
payload: SeedClientMessage;
message: SeedClientMessage;
} | {
type: "wait";
url: string;
queueId: string;
}

export type SeedClientMessage = {
Expand All @@ -27,6 +31,11 @@ export type SeedClientSendOptions = {
queueId: string;
}

export type SeedClientSubscribeOptions = {
queueId: string;
nonce: number;
}

/**
* SeedClient hides all network errors and makes it seem
* to the consumer like if the connection was just slow. So no retries
Expand All @@ -38,8 +47,10 @@ export interface SeedClient {
events: Observable<SeedClientEvent>;

getConnected(): boolean;

send(url: string, options: SeedClientSendOptions): Promise<boolean>;
subscribe(): Promise<void>;

subscribe(url: string, options: SeedClientSubscribeOptions): void;

/**
* SeedClient automatically manages connections
Expand All @@ -63,6 +74,14 @@ export interface CreateSeedClientOptions {
};
}

type ServerEvent = {
type: "new";
message: SeedClientMessage;
} | {
type: "wait";
queueId: string;
}

type SendRequest = {
type: "send";
message: SeedClientSendOptions;
Expand All @@ -72,13 +91,37 @@ type SendResponse = {
status: boolean
}

type SubscribeRequest = {
type: "subscribe";
queueId: string;
nonce: number;
}

export function createSeedClient(
{ engine: engineOptions }: CreateSeedClientOptions,
): SeedClient {
// TODO: proxy events
const events: Observable<SeedClientEvent> = createObservable();

const engine = createSeedEngine(engineOptions.mainUrl);
const subscribeChatIds: Set<string> = new Set();

engine.events.subscribe(event => {
switch (event.type) {
case "connected":
events.emit(event);
break;
case "server":
if (!typia.is<ServerEvent>(event.payload)) break;
events.emit({
url: event.url,
...event.payload,
});
break;
default:
event.type satisfies "ready";
}
});

function getConnected() {
return engine.getReady();
Expand All @@ -99,6 +142,25 @@ export function createSeedClient(
return response.status;
}

function subscribe(url: string, { queueId, nonce }: SeedClientSubscribeOptions) {
if (subscribeChatIds.has(url)) {
throw new Error(`Already subscribed to this chat id ${queueId}`);
}
subscribeChatIds.add(url);
const request: SubscribeRequest = {
type: "subscribe",
queueId, nonce,
};
if (engine.getConnected(url)) {
void engine.executeOrThrow(url, request);
}
engine.events.subscribe(event => {
if (event.type !== "connected") return;
if (event.url !== url) return;
void engine.executeOrThrow(url, request);
});
}

const servers: Set<string> = new Set();

function addServer(url: string) {
Expand All @@ -112,7 +174,6 @@ export function createSeedClient(
if (event.connected) {
// TODO: Do initial subscribes, etc.
} else {
console.log("CONNECT SAFELY", event);
connectUrlSafely(engine, url);
}
});
Expand Down Expand Up @@ -143,6 +204,7 @@ export function createSeedClient(
events,
getConnected,
send,
subscribe,
addServer,
setForeground,
};
Expand Down
79 changes: 79 additions & 0 deletions src/sdk-v2/seed-crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { decryptAes256, encryptAes256, hmacSha256, verifyHmacSha256 } from "./crypto/subtle-crypto";

Check failure on line 1 in src/sdk-v2/seed-crypto.ts

View workflow job for this annotation

GitHub Actions / Build

Cannot find module './crypto/subtle-crypto' or its corresponding type declarations.

export interface DecryptContentOptions {
content: string;
contentIV: string;
signature: string;
key: string;
}

export async function decryptContent({ content, contentIV, signature, key }: DecryptContentOptions): Promise<unknown> {
// Decrypt content
const decryptedJSON = await decryptAes256({
encrypted: content,
iv: contentIV,
key: key,
});

if (!decryptedJSON) {
return;
}

// Check that message-content is signed
const verify = await verifyHmacSha256({
data: "SIGNATURE:" + decryptedJSON.string,
key: key,
signature: signature,
});

if (!verify) {
return;
}

return JSON.parse(decryptedJSON.string);
}

export interface DeriveNextKeyOptions {
key: string;
}

export function deriveNextKey({ key }: DeriveNextKeyOptions): Promise<string> {
return hmacSha256({
data: "NEXT-KEY",
key: key,
});
}

export interface EncryptContentOptions {
content: unknown;
key: string;
}

export interface EncryptContentResult {
contentIV: string;
content: string;
signature: string;
}

export async function encryptContent(
{ content, key }: EncryptContentOptions,
): Promise<EncryptContentResult> {
const contentString = JSON.stringify(content);

const signature = await hmacSha256({
data: "SIGNATURE:" + contentString,
key: key,
});

const encryptedContent = await encryptAes256({
data: contentString,
key: key,
});

return {
contentIV: encryptedContent.iv,
content: encryptedContent.encrypted,
signature: signature,
};
}

11 changes: 9 additions & 2 deletions src/sdk-v2/seed-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ type ConnectRequest = {
url: string;
}

type DisconnectedEvent = {
type: "disconnected";
url: string;
}

type SeedEngineReceivedMessage =
SeedEngineForwardReceivedMessage |
SeedEngineRawReceivedMessage
Expand Down Expand Up @@ -218,7 +223,6 @@ export function createSeedEngine(mainUrl: string): SeedEngine {
};

ws.onmessage = (message) => {
console.log("<<< debug", message.data);
const data = JSON.parse(message.data as string) as SeedEngineReceivedMessage;
if (!typia.is<SeedEngineReceivedMessage>(data)) {
return;
Expand All @@ -242,13 +246,16 @@ export function createSeedEngine(mainUrl: string): SeedEngine {
}
const { resolve } = requests[index];
requests.splice(index, 1);
console.log("FORWARD", forward);
if (forward.forward.response) {
resolve(forward.forward.response);
} else {
resolve(forward.forward);
}
} else {
if (typia.is<DisconnectedEvent>(forward.forward.event)) {
setConnectedUrl(forward.forward.event.url, false);
return;
}
events.emit({
type: "server",
url: forward.url,
Expand Down
2 changes: 1 addition & 1 deletion src/sdk/client/seed-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function createSeedClient(
return response.status;
},

async subscribe({ queueId, nonce }): Promise<void> {
subscribe({ queueId, nonce }): Promise<void> {
const request: SubscribeRequest = {
type: "subscribe",
chatId: queueId,
Expand Down
3 changes: 0 additions & 3 deletions src/sdk/crypto/README.md

This file was deleted.

Loading

0 comments on commit 02e7e52

Please sign in to comment.