jazz-run 0.8.11 → 0.8.13

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # jazz-run
2
2
 
3
+ ## 0.8.13
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [fd011d7]
8
+ - Updated dependencies [e0dd006]
9
+ - jazz-tools@0.8.13
10
+ - cojson-transport-ws@0.8.13
11
+
12
+ ## 0.8.12
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [6ed75eb]
17
+ - cojson-storage-sqlite@0.8.12
18
+ - cojson@0.8.12
19
+ - cojson-transport-ws@0.8.12
20
+ - jazz-tools@0.8.12
21
+
3
22
  ## 0.8.11
4
23
 
5
24
  ### Patch Changes
@@ -0,0 +1,80 @@
1
+ import { createWebSocketPeer } from "cojson-transport-ws";
2
+ import { WebSocket } from "ws";
3
+ import { Account, WasmCrypto, createJazzContext, isControlledAccount, } from "jazz-tools";
4
+ import { fixedCredentialsAuth, randomSessionProvider } from "jazz-tools";
5
+ export const createWorkerAccount = async ({ name, peer: peerAddr, }) => {
6
+ const crypto = await WasmCrypto.create();
7
+ const peer = createWebSocketPeer({
8
+ id: "upstream",
9
+ websocket: new WebSocket(peerAddr),
10
+ role: "server",
11
+ });
12
+ const account = await Account.create({
13
+ creationProps: { name },
14
+ peersToLoadFrom: [peer],
15
+ crypto,
16
+ });
17
+ if (!isControlledAccount(account)) {
18
+ throw new Error("account is not a controlled account");
19
+ }
20
+ const accountCoValue = account._raw.core;
21
+ const accountProfileCoValue = account.profile._raw.core;
22
+ const syncManager = account._raw.core.node.syncManager;
23
+ await Promise.all([
24
+ syncManager.syncCoValue(accountCoValue),
25
+ syncManager.syncCoValue(accountProfileCoValue),
26
+ ]);
27
+ await Promise.all([
28
+ waitForSync(account, peer, accountCoValue),
29
+ waitForSync(account, peer, accountProfileCoValue),
30
+ ]);
31
+ // Spawn a second peer to double check that the account is fully synced
32
+ const peer2 = createWebSocketPeer({
33
+ id: "upstream2",
34
+ websocket: new WebSocket(peerAddr),
35
+ role: "server",
36
+ });
37
+ await createJazzContext({
38
+ auth: fixedCredentialsAuth({
39
+ accountID: account.id,
40
+ secret: account._raw.agentSecret,
41
+ }),
42
+ sessionProvider: randomSessionProvider,
43
+ peersToLoadFrom: [peer2],
44
+ crypto,
45
+ });
46
+ return {
47
+ accountId: account.id,
48
+ agentSecret: account._raw.agentSecret,
49
+ };
50
+ };
51
+ function waitForSync(account, peer, coValue) {
52
+ const syncManager = account._raw.core.node.syncManager;
53
+ const peerState = syncManager.peers[peer.id];
54
+ return new Promise((resolve) => {
55
+ const unsubscribe = peerState?.optimisticKnownStates.subscribe((id, peerKnownState) => {
56
+ if (id !== coValue.id)
57
+ return;
58
+ const knownState = coValue.knownState();
59
+ const synced = isEqualSession(knownState.sessions, peerKnownState.sessions);
60
+ if (synced) {
61
+ resolve(true);
62
+ unsubscribe?.();
63
+ }
64
+ });
65
+ });
66
+ }
67
+ function isEqualSession(a, b) {
68
+ const keysA = Object.keys(a);
69
+ const keysB = Object.keys(b);
70
+ if (keysA.length !== keysB.length) {
71
+ return false;
72
+ }
73
+ for (const sessionId of keysA) {
74
+ if (a[sessionId] !== b[sessionId]) {
75
+ return false;
76
+ }
77
+ }
78
+ return true;
79
+ }
80
+ //# sourceMappingURL=createWorkerAccount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createWorkerAccount.js","sourceRoot":"","sources":["../src/createWorkerAccount.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,15 +1,45 @@
1
1
  #!/usr/bin/env node
2
2
  /* istanbul ignore file -- @preserve */
3
- import { Command } from "@effect/cli";
3
+ import { Command, Options } from "@effect/cli";
4
4
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
5
- import { Effect } from "effect";
6
- import { account } from "./accountCreate.js";
7
- import { startSync } from "./startSync.js";
5
+ import { Console, Effect } from "effect";
6
+ import { createWorkerAccount } from "./createWorkerAccount.js";
7
+ import { startSyncServer } from "./startSyncServer.js";
8
8
  const jazzTools = Command.make("jazz-tools");
9
- const command = jazzTools.pipe(Command.withSubcommands([account, startSync]));
9
+ const nameOption = Options.text("name").pipe(Options.withAlias("n"));
10
+ const peerOption = Options.text("peer")
11
+ .pipe(Options.withAlias("p"))
12
+ .pipe(Options.withDefault("wss://cloud.jazz.tools"));
13
+ const createAccountCommand = Command.make("create", { name: nameOption, peer: peerOption }, ({ name, peer }) => {
14
+ return Effect.gen(function* () {
15
+ const { accountId, agentSecret } = yield* Effect.promise(() => createWorkerAccount({ name, peer }));
16
+ yield* Console.log(`# Credentials for Jazz account "${name}":
17
+ JAZZ_WORKER_ACCOUNT=${accountId}
18
+ JAZZ_WORKER_SECRET=${agentSecret}
19
+ `);
20
+ });
21
+ });
22
+ const accountCommand = Command.make("account").pipe(Command.withSubcommands([createAccountCommand]));
23
+ const portOption = Options.text("port")
24
+ .pipe(Options.withAlias("p"))
25
+ .pipe(Options.withDescription("Select a different port for the WebSocket server. Default is 4200"))
26
+ .pipe(Options.withDefault("4200"));
27
+ const inMemoryOption = Options.boolean("in-memory").pipe(Options.withDescription("Use an in-memory storage instead of file-based"));
28
+ const dbOption = Options.file("db")
29
+ .pipe(Options.withDescription("The path to the file where to store the data. Default is 'sync-db/storage.db'"))
30
+ .pipe(Options.withDefault("sync-db/storage.db"));
31
+ export const startSyncServerCommand = Command.make("sync", { port: portOption, inMemory: inMemoryOption, db: dbOption }, ({ port, inMemory, db }) => {
32
+ return Effect.gen(function* () {
33
+ yield* Effect.promise(() => startSyncServer({ port, inMemory, db }));
34
+ Console.log(`COJSON sync server listening on ws://127.0.0.1:${port}`);
35
+ // Keep the server up
36
+ yield* Effect.never;
37
+ });
38
+ });
39
+ const command = jazzTools.pipe(Command.withSubcommands([accountCommand, startSyncServerCommand]));
10
40
  const cli = Command.run(command, {
11
41
  name: "Jazz CLI Tools",
12
- version: "v0.7.0",
42
+ version: "v0.8.11",
13
43
  });
14
44
  Effect.suspend(() => cli(process.argv)).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain);
15
45
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,uCAAuC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AAE7C,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;AAE9E,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE;IAC7B,IAAI,EAAE,gBAAgB;IACtB,OAAO,EAAE,QAAQ;CACpB,CAAC,CAAC;AAEH,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CACxC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,EACjC,WAAW,CAAC,OAAO,CACtB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,uCAAuC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AAE7C,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;AACrE,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;KAClC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;KAC5B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,wBAAwB,CAAC,CAAC,CAAC;AAEzD,MAAM,oBAAoB,GAAG,OAAO,CAAC,IAAI,CACrC,QAAQ,EACR,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,EACtC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE;IACf,OAAO,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACvB,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAC1D,mBAAmB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CACtC,CAAC;QAEF,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,mCAAmC,IAAI;sBAChD,SAAS;qBACV,WAAW;CAC/B,CAAC,CAAC;IACK,CAAC,CAAC,CAAC;AACP,CAAC,CACJ,CAAC;AAEF,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAC/C,OAAO,CAAC,eAAe,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAClD,CAAC;AAEF,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;KAClC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;KAC5B,IAAI,CACD,OAAO,CAAC,eAAe,CACnB,mEAAmE,CACtE,CACJ;KACA,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;AAEvC,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,IAAI,CACpD,OAAO,CAAC,eAAe,CAAC,gDAAgD,CAAC,CAC5E,CAAC;AAEF,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;KAC9B,IAAI,CACD,OAAO,CAAC,eAAe,CACnB,+EAA+E,CAClF,CACJ;KACA,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,oBAAoB,CAAC,CAAC,CAAC;AAErD,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAC9C,MAAM,EACN,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,EAC5D,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE;IACvB,OAAO,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACvB,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CACvB,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAC1C,CAAC;QAEF,OAAO,CAAC,GAAG,CACP,kDAAkD,IAAI,EAAE,CAC3D,CAAC;QAEF,qBAAqB;QACrB,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;IACxB,CAAC,CAAC,CAAC;AACP,CAAC,CACJ,CAAC;AAEF,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAC1B,OAAO,CAAC,eAAe,CAAC,CAAC,cAAc,EAAE,sBAAsB,CAAC,CAAC,CACpE,CAAC;AAEF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE;IAC7B,IAAI,EAAE,gBAAgB;IACtB,OAAO,EAAE,SAAS;CACrB,CAAC,CAAC;AAEH,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CACxC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,EACjC,WAAW,CAAC,OAAO,CACtB,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { ControlledAgent, LocalNode, WasmCrypto } from "cojson";
2
+ import { WebSocketServer } from "ws";
3
+ import { createServer } from "http";
4
+ import { createWebSocketPeer } from "cojson-transport-ws";
5
+ import { SQLiteStorage } from "cojson-storage-sqlite";
6
+ import { dirname } from "node:path";
7
+ import { mkdir } from "node:fs/promises";
8
+ export const startSyncServer = async ({ port, inMemory, db, }) => {
9
+ const crypto = await WasmCrypto.create();
10
+ const server = createServer((req, res) => {
11
+ if (req.url === "/health") {
12
+ res.writeHead(200);
13
+ res.end("ok");
14
+ }
15
+ });
16
+ const wss = new WebSocketServer({ noServer: true });
17
+ const agentSecret = crypto.newRandomAgentSecret();
18
+ const agentID = crypto.getAgentID(agentSecret);
19
+ const localNode = new LocalNode(new ControlledAgent(agentSecret, crypto), crypto.newRandomSessionID(agentID), crypto);
20
+ if (!inMemory) {
21
+ await mkdir(dirname(db), { recursive: true });
22
+ const storage = await SQLiteStorage.asPeer({ filename: db });
23
+ localNode.syncManager.addPeer(storage);
24
+ }
25
+ wss.on("connection", function connection(ws, req) {
26
+ // ping/pong for the connection liveness
27
+ const pinging = setInterval(() => {
28
+ ws.send(JSON.stringify({
29
+ type: "ping",
30
+ time: Date.now(),
31
+ dc: "unknown",
32
+ }));
33
+ }, 1500);
34
+ ws.on("close", () => {
35
+ clearInterval(pinging);
36
+ });
37
+ const clientAddress = req.headers["x-forwarded-for"]
38
+ ?.split(",")[0]
39
+ ?.trim() || req.socket.remoteAddress;
40
+ const clientId = clientAddress + "@" + new Date().toISOString();
41
+ localNode.syncManager.addPeer(createWebSocketPeer({
42
+ id: clientId,
43
+ role: "client",
44
+ websocket: ws,
45
+ expectPings: false,
46
+ batchingByDefault: false,
47
+ }));
48
+ ws.on("error", (e) => console.error(`Error on connection ${clientId}:`, e));
49
+ });
50
+ server.on("upgrade", function upgrade(req, socket, head) {
51
+ if (req.url !== "/health") {
52
+ wss.handleUpgrade(req, socket, head, function done(ws) {
53
+ wss.emit("connection", ws, req);
54
+ });
55
+ }
56
+ });
57
+ server.listen(port ? parseInt(port) : undefined);
58
+ return server;
59
+ };
60
+ //# sourceMappingURL=startSyncServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startSyncServer.js","sourceRoot":"","sources":["../src/startSyncServer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { describe, onTestFinished, it, expect } from "vitest";
2
+ import { startSyncServer } from "../startSyncServer.js";
3
+ import { createWorkerAccount } from "../createWorkerAccount.js";
4
+ describe("createWorkerAccount - integration tests", () => {
5
+ it("should create a worker account using the local sync server", async () => {
6
+ // Pass port: undefined to let the server choose a random port
7
+ const server = await startSyncServer({ port: undefined, inMemory: true, db: '' });
8
+ onTestFinished(() => {
9
+ server.close();
10
+ });
11
+ const address = server.address();
12
+ if (typeof address !== 'object' || address === null) {
13
+ throw new Error('Server address is not an object');
14
+ }
15
+ const { accountId, agentSecret } = await createWorkerAccount({ name: "test", peer: `ws://localhost:${address.port}` });
16
+ expect(accountId).toBeDefined();
17
+ expect(agentSecret).toBeDefined();
18
+ });
19
+ it("should create a worker account using the Jazz cloud", async () => {
20
+ const { accountId, agentSecret } = await createWorkerAccount({ name: "test", peer: `wss://cloud.jazz.tools` });
21
+ expect(accountId).toBeDefined();
22
+ expect(agentSecret).toBeDefined();
23
+ });
24
+ });
25
+ //# sourceMappingURL=createWorkerAccount.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createWorkerAccount.test.js","sourceRoot":"","sources":["../../src/test/createWorkerAccount.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAEhE,QAAQ,CAAC,yCAAyC,EAAE,GAAG,EAAE;IACrD,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QACxE,8DAA8D;QAC9D,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAElF,cAAc,CAAC,GAAG,EAAE;YAChB,MAAM,CAAC,KAAK,EAAE,CAAA;QAClB,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;QAEjC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACvD,CAAC;QAED,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,MAAM,mBAAmB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAEvH,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;QAChC,MAAM,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,MAAM,mBAAmB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,EAAE,CAAC,CAAC;QAE/G,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;QAChC,MAAM,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "bin": "./dist/index.js",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
- "version": "0.8.11",
6
+ "version": "0.8.13",
7
7
  "dependencies": {
8
8
  "@effect/cli": "^0.41.2",
9
9
  "@effect/platform-node": "^0.57.2",
@@ -11,11 +11,11 @@
11
11
  "@effect/printer-ansi": "^0.34.5",
12
12
  "@effect/schema": "^0.71.1",
13
13
  "@effect/typeclass": "^0.25.5",
14
- "cojson": "0.8.11",
15
- "cojson-storage-sqlite": "0.8.11",
16
- "cojson-transport-ws": "0.8.11",
14
+ "cojson": "0.8.12",
15
+ "cojson-storage-sqlite": "0.8.12",
16
+ "cojson-transport-ws": "0.8.13",
17
17
  "effect": "^3.6.5",
18
- "jazz-tools": "0.8.11",
18
+ "jazz-tools": "0.8.13",
19
19
  "ws": "^8.14.2"
20
20
  },
21
21
  "devDependencies": {
@@ -0,0 +1,114 @@
1
+ import { createWebSocketPeer } from "cojson-transport-ws";
2
+ import { WebSocket } from "ws";
3
+ import {
4
+ Account,
5
+ Peer,
6
+ WasmCrypto,
7
+ createJazzContext,
8
+ isControlledAccount,
9
+ } from "jazz-tools";
10
+ import { fixedCredentialsAuth, randomSessionProvider } from "jazz-tools";
11
+ import { CoValueCore } from "cojson";
12
+
13
+ export const createWorkerAccount = async ({
14
+ name,
15
+ peer: peerAddr,
16
+ }: {
17
+ name: string;
18
+ peer: string;
19
+ }) => {
20
+ const crypto = await WasmCrypto.create();
21
+
22
+ const peer = createWebSocketPeer({
23
+ id: "upstream",
24
+ websocket: new WebSocket(peerAddr),
25
+ role: "server",
26
+ });
27
+
28
+ const account = await Account.create({
29
+ creationProps: { name },
30
+ peersToLoadFrom: [peer],
31
+ crypto,
32
+ });
33
+
34
+ if (!isControlledAccount(account)) {
35
+ throw new Error("account is not a controlled account");
36
+ }
37
+
38
+ const accountCoValue = account._raw.core;
39
+ const accountProfileCoValue = account.profile!._raw.core;
40
+ const syncManager = account._raw.core.node.syncManager;
41
+
42
+ await Promise.all([
43
+ syncManager.syncCoValue(accountCoValue),
44
+ syncManager.syncCoValue(accountProfileCoValue),
45
+ ]);
46
+
47
+ await Promise.all([
48
+ waitForSync(account, peer, accountCoValue),
49
+ waitForSync(account, peer, accountProfileCoValue),
50
+ ]);
51
+
52
+ // Spawn a second peer to double check that the account is fully synced
53
+ const peer2 = createWebSocketPeer({
54
+ id: "upstream2",
55
+ websocket: new WebSocket(peerAddr),
56
+ role: "server",
57
+ });
58
+
59
+ await createJazzContext({
60
+ auth: fixedCredentialsAuth({
61
+ accountID: account.id,
62
+ secret: account._raw.agentSecret,
63
+ }),
64
+ sessionProvider: randomSessionProvider,
65
+ peersToLoadFrom: [peer2],
66
+ crypto,
67
+ });
68
+
69
+ return {
70
+ accountId: account.id,
71
+ agentSecret: account._raw.agentSecret,
72
+ };
73
+ };
74
+
75
+ function waitForSync(account: Account, peer: Peer, coValue: CoValueCore) {
76
+ const syncManager = account._raw.core.node.syncManager;
77
+ const peerState = syncManager.peers[peer.id];
78
+
79
+ return new Promise((resolve) => {
80
+ const unsubscribe = peerState?.optimisticKnownStates.subscribe(
81
+ (id, peerKnownState) => {
82
+ if (id !== coValue.id) return;
83
+
84
+ const knownState = coValue.knownState();
85
+
86
+ const synced = isEqualSession(
87
+ knownState.sessions,
88
+ peerKnownState.sessions,
89
+ );
90
+ if (synced) {
91
+ resolve(true);
92
+ unsubscribe?.();
93
+ }
94
+ },
95
+ );
96
+ });
97
+ }
98
+
99
+ function isEqualSession(a: Record<string, number>, b: Record<string, number>) {
100
+ const keysA = Object.keys(a);
101
+ const keysB = Object.keys(b);
102
+
103
+ if (keysA.length !== keysB.length) {
104
+ return false;
105
+ }
106
+
107
+ for (const sessionId of keysA) {
108
+ if (a[sessionId] !== b[sessionId]) {
109
+ return false;
110
+ }
111
+ }
112
+
113
+ return true;
114
+ }
package/src/index.ts CHANGED
@@ -1,18 +1,86 @@
1
1
  #!/usr/bin/env node
2
2
  /* istanbul ignore file -- @preserve */
3
- import { Command } from "@effect/cli";
3
+ import { Command, Options } from "@effect/cli";
4
4
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
5
- import { Effect } from "effect";
6
- import { account } from "./accountCreate.js";
7
- import { startSync } from "./startSync.js";
5
+ import { Console, Effect } from "effect";
6
+ import { createWorkerAccount } from "./createWorkerAccount.js";
7
+ import { startSyncServer } from "./startSyncServer.js";
8
8
 
9
9
  const jazzTools = Command.make("jazz-tools");
10
10
 
11
- const command = jazzTools.pipe(Command.withSubcommands([account, startSync]));
11
+ const nameOption = Options.text("name").pipe(Options.withAlias("n"));
12
+ const peerOption = Options.text("peer")
13
+ .pipe(Options.withAlias("p"))
14
+ .pipe(Options.withDefault("wss://cloud.jazz.tools"));
15
+
16
+ const createAccountCommand = Command.make(
17
+ "create",
18
+ { name: nameOption, peer: peerOption },
19
+ ({ name, peer }) => {
20
+ return Effect.gen(function* () {
21
+ const { accountId, agentSecret } = yield* Effect.promise(() =>
22
+ createWorkerAccount({ name, peer }),
23
+ );
24
+
25
+ yield* Console.log(`# Credentials for Jazz account "${name}":
26
+ JAZZ_WORKER_ACCOUNT=${accountId}
27
+ JAZZ_WORKER_SECRET=${agentSecret}
28
+ `);
29
+ });
30
+ },
31
+ );
32
+
33
+ const accountCommand = Command.make("account").pipe(
34
+ Command.withSubcommands([createAccountCommand]),
35
+ );
36
+
37
+ const portOption = Options.text("port")
38
+ .pipe(Options.withAlias("p"))
39
+ .pipe(
40
+ Options.withDescription(
41
+ "Select a different port for the WebSocket server. Default is 4200",
42
+ ),
43
+ )
44
+ .pipe(Options.withDefault("4200"));
45
+
46
+ const inMemoryOption = Options.boolean("in-memory").pipe(
47
+ Options.withDescription("Use an in-memory storage instead of file-based"),
48
+ );
49
+
50
+ const dbOption = Options.file("db")
51
+ .pipe(
52
+ Options.withDescription(
53
+ "The path to the file where to store the data. Default is 'sync-db/storage.db'",
54
+ ),
55
+ )
56
+ .pipe(Options.withDefault("sync-db/storage.db"));
57
+
58
+ export const startSyncServerCommand = Command.make(
59
+ "sync",
60
+ { port: portOption, inMemory: inMemoryOption, db: dbOption },
61
+ ({ port, inMemory, db }) => {
62
+ return Effect.gen(function* () {
63
+ yield* Effect.promise(() =>
64
+ startSyncServer({ port, inMemory, db }),
65
+ );
66
+
67
+ Console.log(
68
+ `COJSON sync server listening on ws://127.0.0.1:${port}`,
69
+ );
70
+
71
+ // Keep the server up
72
+ yield* Effect.never;
73
+ });
74
+ },
75
+ );
76
+
77
+ const command = jazzTools.pipe(
78
+ Command.withSubcommands([accountCommand, startSyncServerCommand]),
79
+ );
12
80
 
13
81
  const cli = Command.run(command, {
14
82
  name: "Jazz CLI Tools",
15
- version: "v0.7.0",
83
+ version: "v0.8.11",
16
84
  });
17
85
 
18
86
  Effect.suspend(() => cli(process.argv)).pipe(
@@ -0,0 +1,95 @@
1
+ import { ControlledAgent, LocalNode, WasmCrypto } from "cojson";
2
+ import { WebSocketServer } from "ws";
3
+ import { createServer } from "http";
4
+
5
+ import { createWebSocketPeer } from "cojson-transport-ws";
6
+ import { SQLiteStorage } from "cojson-storage-sqlite";
7
+ import { dirname } from "node:path";
8
+ import { mkdir } from "node:fs/promises";
9
+
10
+ export const startSyncServer = async ({
11
+ port,
12
+ inMemory,
13
+ db,
14
+ }: {
15
+ port: string | undefined;
16
+ inMemory: boolean;
17
+ db: string;
18
+ }) => {
19
+ const crypto = await WasmCrypto.create();
20
+
21
+ const server = createServer((req, res) => {
22
+ if (req.url === "/health") {
23
+ res.writeHead(200);
24
+ res.end("ok");
25
+ }
26
+ });
27
+ const wss = new WebSocketServer({ noServer: true });
28
+
29
+ const agentSecret = crypto.newRandomAgentSecret();
30
+ const agentID = crypto.getAgentID(agentSecret);
31
+
32
+ const localNode = new LocalNode(
33
+ new ControlledAgent(agentSecret, crypto),
34
+ crypto.newRandomSessionID(agentID),
35
+ crypto,
36
+ );
37
+
38
+ if (!inMemory) {
39
+ await mkdir(dirname(db), { recursive: true })
40
+
41
+ const storage = await SQLiteStorage.asPeer({ filename: db });
42
+
43
+ localNode.syncManager.addPeer(storage);
44
+ }
45
+
46
+ wss.on("connection", function connection(ws, req) {
47
+ // ping/pong for the connection liveness
48
+ const pinging = setInterval(() => {
49
+ ws.send(
50
+ JSON.stringify({
51
+ type: "ping",
52
+ time: Date.now(),
53
+ dc: "unknown",
54
+ }),
55
+ );
56
+ }, 1500);
57
+
58
+ ws.on("close", () => {
59
+ clearInterval(pinging);
60
+ });
61
+
62
+ const clientAddress =
63
+ (req.headers["x-forwarded-for"] as string | undefined)
64
+ ?.split(",")[0]
65
+ ?.trim() || req.socket.remoteAddress;
66
+
67
+ const clientId = clientAddress + "@" + new Date().toISOString();
68
+
69
+ localNode.syncManager.addPeer(
70
+ createWebSocketPeer({
71
+ id: clientId,
72
+ role: "client",
73
+ websocket: ws,
74
+ expectPings: false,
75
+ batchingByDefault: false,
76
+ }),
77
+ );
78
+
79
+ ws.on("error", (e) =>
80
+ console.error(`Error on connection ${clientId}:`, e),
81
+ );
82
+ });
83
+
84
+ server.on("upgrade", function upgrade(req, socket, head) {
85
+ if (req.url !== "/health") {
86
+ wss.handleUpgrade(req, socket, head, function done(ws) {
87
+ wss.emit("connection", ws, req);
88
+ });
89
+ }
90
+ });
91
+
92
+ server.listen(port ? parseInt(port) : undefined);
93
+
94
+ return server;
95
+ };
@@ -0,0 +1,32 @@
1
+ import { describe, onTestFinished, it, expect } from "vitest";
2
+ import { startSyncServer } from "../startSyncServer.js";
3
+ import { createWorkerAccount } from "../createWorkerAccount.js";
4
+
5
+ describe("createWorkerAccount - integration tests", () => {
6
+ it("should create a worker account using the local sync server", async () => {
7
+ // Pass port: undefined to let the server choose a random port
8
+ const server = await startSyncServer({ port: undefined, inMemory: true, db: '' });
9
+
10
+ onTestFinished(() => {
11
+ server.close()
12
+ });
13
+
14
+ const address = server.address();
15
+
16
+ if (typeof address !== 'object' || address === null) {
17
+ throw new Error('Server address is not an object');
18
+ }
19
+
20
+ const { accountId, agentSecret } = await createWorkerAccount({ name: "test", peer: `ws://localhost:${address.port}` });
21
+
22
+ expect(accountId).toBeDefined();
23
+ expect(agentSecret).toBeDefined();
24
+ });
25
+
26
+ it("should create a worker account using the Jazz cloud", async () => {
27
+ const { accountId, agentSecret } = await createWorkerAccount({ name: "test", peer: `wss://cloud.jazz.tools` });
28
+
29
+ expect(accountId).toBeDefined();
30
+ expect(agentSecret).toBeDefined();
31
+ });
32
+ });
@@ -1,88 +0,0 @@
1
- import { Command, Options } from "@effect/cli";
2
- import { Console, Effect } from "effect";
3
- import { createWebSocketPeer } from "cojson-transport-ws";
4
- import { WebSocket } from "ws";
5
- import { Account, WasmCrypto, createJazzContext, isControlledAccount, } from "jazz-tools";
6
- import { fixedCredentialsAuth, randomSessionProvider } from "jazz-tools";
7
- const name = Options.text("name").pipe(Options.withAlias("n"));
8
- const peer = Options.text("peer")
9
- .pipe(Options.withAlias("p"))
10
- .pipe(Options.withDefault("wss://cloud.jazz.tools"));
11
- const accountCreate = Command.make("create", { name, peer }, ({ name, peer: peerAddr }) => {
12
- return Effect.gen(function* () {
13
- const crypto = yield* Effect.promise(() => WasmCrypto.create());
14
- const peer = createWebSocketPeer({
15
- id: "upstream",
16
- websocket: new WebSocket(peerAddr),
17
- role: "server",
18
- });
19
- const account = yield* Effect.promise(async () => Account.create({
20
- creationProps: { name },
21
- peersToLoadFrom: [peer],
22
- crypto,
23
- }));
24
- if (!isControlledAccount(account)) {
25
- throw new Error("account is not a controlled account");
26
- }
27
- const accountCoValue = account._raw.core;
28
- const accountProfileCoValue = account.profile._raw.core;
29
- const syncManager = account._raw.core.node.syncManager;
30
- yield* Effect.promise(() => syncManager.syncCoValue(accountCoValue));
31
- yield* Effect.promise(() => syncManager.syncCoValue(accountProfileCoValue));
32
- yield* Effect.promise(() => Promise.all([
33
- waitForSync(account, peer, accountCoValue),
34
- waitForSync(account, peer, accountProfileCoValue),
35
- ]));
36
- // Spawn a second peer to double check that the account is fully synced
37
- const peer2 = createWebSocketPeer({
38
- id: "upstream2",
39
- websocket: new WebSocket(peerAddr),
40
- role: "server",
41
- });
42
- yield* Effect.promise(async () => createJazzContext({
43
- auth: fixedCredentialsAuth({
44
- accountID: account.id,
45
- secret: account._raw.agentSecret,
46
- }),
47
- sessionProvider: randomSessionProvider,
48
- peersToLoadFrom: [peer2],
49
- crypto,
50
- }));
51
- yield* Console.log(`# Credentials for Jazz account "${name}":
52
- JAZZ_WORKER_ACCOUNT=${account.id}
53
- JAZZ_WORKER_SECRET=${account._raw.agentSecret}
54
- `);
55
- });
56
- });
57
- const accountBase = Command.make("account");
58
- export const account = accountBase.pipe(Command.withSubcommands([accountCreate]));
59
- function waitForSync(account, peer, coValue) {
60
- const syncManager = account._raw.core.node.syncManager;
61
- const peerState = syncManager.peers[peer.id];
62
- return new Promise((resolve) => {
63
- const unsubscribe = peerState?.optimisticKnownStates.subscribe((id, peerKnownState) => {
64
- if (id !== coValue.id)
65
- return;
66
- const knownState = coValue.knownState();
67
- const synced = isEqualSession(knownState.sessions, peerKnownState.sessions);
68
- if (synced) {
69
- resolve(true);
70
- unsubscribe?.();
71
- }
72
- });
73
- });
74
- }
75
- function isEqualSession(a, b) {
76
- const keysA = Object.keys(a);
77
- const keysB = Object.keys(b);
78
- if (keysA.length !== keysB.length) {
79
- return false;
80
- }
81
- for (const sessionId of keysA) {
82
- if (a[sessionId] !== b[sessionId]) {
83
- return false;
84
- }
85
- }
86
- return true;
87
- }
88
- //# sourceMappingURL=accountCreate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"accountCreate.js","sourceRoot":"","sources":["../src/accountCreate.ts"],"names":[],"mappings":"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"}
package/dist/startSync.js DELETED
@@ -1,76 +0,0 @@
1
- /* istanbul ignore file -- @preserve */
2
- import { Command, Options } from "@effect/cli";
3
- import { ControlledAgent, LocalNode, WasmCrypto } from "cojson";
4
- import { WebSocketServer } from "ws";
5
- import { createServer } from "http";
6
- import { createWebSocketPeer } from "cojson-transport-ws";
7
- import { Effect } from "effect";
8
- import { SQLiteStorage } from "cojson-storage-sqlite";
9
- import { dirname } from "node:path";
10
- import { mkdir } from "node:fs/promises";
11
- const port = Options.text("port")
12
- .pipe(Options.withAlias("p"))
13
- .pipe(Options.withDescription("Select a different port for the WebSocket server. Default is 4200"))
14
- .pipe(Options.withDefault("4200"));
15
- const inMemory = Options.boolean("in-memory").pipe(Options.withDescription("Use an in-memory storage instead of file-based"));
16
- const db = Options.file("db")
17
- .pipe(Options.withDescription("The path to the file where to store the data. Default is 'sync-db/storage.db'"))
18
- .pipe(Options.withDefault("sync-db/storage.db"));
19
- export const startSync = Command.make("sync", { port, inMemory, db }, ({ port, inMemory, db }) => {
20
- return Effect.gen(function* () {
21
- const crypto = yield* Effect.promise(() => WasmCrypto.create());
22
- const server = createServer((req, res) => {
23
- if (req.url === "/health") {
24
- res.writeHead(200);
25
- res.end("ok");
26
- }
27
- });
28
- const wss = new WebSocketServer({ noServer: true });
29
- console.log("COJSON sync server listening on port " + port);
30
- const agentSecret = crypto.newRandomAgentSecret();
31
- const agentID = crypto.getAgentID(agentSecret);
32
- const localNode = new LocalNode(new ControlledAgent(agentSecret, crypto), crypto.newRandomSessionID(agentID), crypto);
33
- if (!inMemory) {
34
- yield* Effect.promise(() => mkdir(dirname(db), { recursive: true }));
35
- const storage = yield* Effect.promise(() => SQLiteStorage.asPeer({ filename: db }));
36
- localNode.syncManager.addPeer(storage);
37
- }
38
- wss.on("connection", function connection(ws, req) {
39
- // ping/pong for the connection liveness
40
- const pinging = setInterval(() => {
41
- ws.send(JSON.stringify({
42
- type: "ping",
43
- time: Date.now(),
44
- dc: "unknown",
45
- }));
46
- }, 1500);
47
- ws.on("close", () => {
48
- clearInterval(pinging);
49
- });
50
- const clientAddress = req.headers["x-forwarded-for"]
51
- ?.split(",")[0]
52
- ?.trim() || req.socket.remoteAddress;
53
- const clientId = clientAddress + "@" + new Date().toISOString();
54
- localNode.syncManager.addPeer(createWebSocketPeer({
55
- id: clientId,
56
- role: "client",
57
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
- websocket: ws, // TODO: fix types
59
- expectPings: false,
60
- batchingByDefault: false
61
- }));
62
- ws.on("error", (e) => console.error(`Error on connection ${clientId}:`, e));
63
- });
64
- server.on("upgrade", function upgrade(req, socket, head) {
65
- if (req.url !== "/health") {
66
- wss.handleUpgrade(req, socket, head, function done(ws) {
67
- wss.emit("connection", ws, req);
68
- });
69
- }
70
- });
71
- server.listen(parseInt(port));
72
- // Keep the server up
73
- yield* Effect.never;
74
- });
75
- });
76
- //# sourceMappingURL=startSync.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"startSync.js","sourceRoot":"","sources":["../src/startSync.ts"],"names":[],"mappings":"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"}
@@ -1,125 +0,0 @@
1
- import { Command, Options } from "@effect/cli";
2
- import { Console, Effect } from "effect";
3
- import { createWebSocketPeer } from "cojson-transport-ws";
4
- import { WebSocket } from "ws";
5
- import {
6
- Account,
7
- Peer,
8
- WasmCrypto,
9
- createJazzContext,
10
- isControlledAccount,
11
- } from "jazz-tools";
12
- import { fixedCredentialsAuth, randomSessionProvider } from "jazz-tools";
13
- import { CoValueCore } from "cojson";
14
-
15
- const name = Options.text("name").pipe(Options.withAlias("n"));
16
- const peer = Options.text("peer")
17
- .pipe(Options.withAlias("p"))
18
- .pipe(Options.withDefault("wss://cloud.jazz.tools"));
19
-
20
- const accountCreate = Command.make(
21
- "create",
22
- { name, peer },
23
- ({ name, peer: peerAddr }) => {
24
- return Effect.gen(function* () {
25
- const crypto = yield* Effect.promise(() => WasmCrypto.create());
26
-
27
- const peer = createWebSocketPeer({
28
- id: "upstream",
29
- websocket: new WebSocket(peerAddr),
30
- role: "server",
31
- });
32
-
33
- const account: Account = yield* Effect.promise(async () =>
34
- Account.create({
35
- creationProps: { name },
36
- peersToLoadFrom: [peer],
37
- crypto,
38
- }),
39
- );
40
- if (!isControlledAccount(account)) {
41
- throw new Error("account is not a controlled account");
42
- }
43
-
44
- const accountCoValue = account._raw.core;
45
- const accountProfileCoValue = account.profile!._raw.core;
46
- const syncManager = account._raw.core.node.syncManager;
47
-
48
- yield* Effect.promise(() =>
49
- syncManager.syncCoValue(accountCoValue),
50
- );
51
- yield* Effect.promise(() =>
52
- syncManager.syncCoValue(accountProfileCoValue),
53
- );
54
-
55
- yield* Effect.promise(() => Promise.all([
56
- waitForSync(account, peer, accountCoValue),
57
- waitForSync(account, peer, accountProfileCoValue),
58
- ]));
59
-
60
- // Spawn a second peer to double check that the account is fully synced
61
- const peer2 = createWebSocketPeer({
62
- id: "upstream2",
63
- websocket: new WebSocket(peerAddr),
64
- role: "server",
65
- });
66
-
67
- yield* Effect.promise(async () =>
68
- createJazzContext({
69
- auth: fixedCredentialsAuth({
70
- accountID: account.id,
71
- secret: account._raw.agentSecret,
72
- }),
73
- sessionProvider: randomSessionProvider,
74
- peersToLoadFrom: [peer2],
75
- crypto,
76
- }),
77
- );
78
-
79
- yield* Console.log(`# Credentials for Jazz account "${name}":
80
- JAZZ_WORKER_ACCOUNT=${account.id}
81
- JAZZ_WORKER_SECRET=${account._raw.agentSecret}
82
- `);
83
- });
84
- },
85
- );
86
-
87
- const accountBase = Command.make("account");
88
-
89
- export const account = accountBase.pipe(Command.withSubcommands([accountCreate]));
90
-
91
- function waitForSync(account: Account, peer: Peer, coValue: CoValueCore) {
92
- const syncManager = account._raw.core.node.syncManager;
93
- const peerState = syncManager.peers[peer.id];
94
-
95
- return new Promise((resolve) => {
96
- const unsubscribe = peerState?.optimisticKnownStates.subscribe((id, peerKnownState) => {
97
- if (id !== coValue.id) return;
98
-
99
- const knownState = coValue.knownState();
100
-
101
- const synced = isEqualSession(knownState.sessions, peerKnownState.sessions);
102
- if (synced) {
103
- resolve(true);
104
- unsubscribe?.();
105
- }
106
- });
107
- });
108
- }
109
-
110
- function isEqualSession(a: Record<string, number>, b: Record<string, number>) {
111
- const keysA = Object.keys(a);
112
- const keysB = Object.keys(b);
113
-
114
- if (keysA.length !== keysB.length) {
115
- return false;
116
- }
117
-
118
- for (const sessionId of keysA) {
119
- if (a[sessionId] !== b[sessionId]) {
120
- return false;
121
- }
122
- }
123
-
124
- return true;
125
- }
package/src/startSync.ts DELETED
@@ -1,125 +0,0 @@
1
- /* istanbul ignore file -- @preserve */
2
- import { Command, Options } from "@effect/cli";
3
- import { ControlledAgent, LocalNode, WasmCrypto } from "cojson";
4
- import { WebSocketServer } from "ws";
5
- import { createServer } from "http";
6
-
7
- import { createWebSocketPeer } from "cojson-transport-ws";
8
- import { Effect } from "effect";
9
- import { SQLiteStorage } from "cojson-storage-sqlite";
10
- import { dirname } from "node:path";
11
- import { mkdir } from "node:fs/promises";
12
-
13
- const port = Options.text("port")
14
- .pipe(Options.withAlias("p"))
15
- .pipe(
16
- Options.withDescription(
17
- "Select a different port for the WebSocket server. Default is 4200",
18
- ),
19
- )
20
- .pipe(Options.withDefault("4200"));
21
-
22
- const inMemory = Options.boolean("in-memory").pipe(
23
- Options.withDescription("Use an in-memory storage instead of file-based"),
24
- );
25
-
26
- const db = Options.file("db")
27
- .pipe(
28
- Options.withDescription(
29
- "The path to the file where to store the data. Default is 'sync-db/storage.db'",
30
- ),
31
- )
32
- .pipe(Options.withDefault("sync-db/storage.db"));
33
-
34
- export const startSync = Command.make(
35
- "sync",
36
- { port, inMemory, db },
37
- ({ port, inMemory, db }) => {
38
- return Effect.gen(function* () {
39
- const crypto = yield* Effect.promise(() => WasmCrypto.create());
40
-
41
- const server = createServer((req, res) => {
42
- if (req.url === "/health") {
43
- res.writeHead(200);
44
- res.end("ok");
45
- }
46
- });
47
- const wss = new WebSocketServer({ noServer: true });
48
-
49
- console.log("COJSON sync server listening on port " + port);
50
-
51
- const agentSecret = crypto.newRandomAgentSecret();
52
- const agentID = crypto.getAgentID(agentSecret);
53
-
54
- const localNode = new LocalNode(
55
- new ControlledAgent(agentSecret, crypto),
56
- crypto.newRandomSessionID(agentID),
57
- crypto,
58
- );
59
-
60
- if (!inMemory) {
61
- yield* Effect.promise(() =>
62
- mkdir(dirname(db), { recursive: true }),
63
- );
64
-
65
- const storage = yield* Effect.promise(() =>
66
- SQLiteStorage.asPeer({ filename: db }),
67
- );
68
-
69
- localNode.syncManager.addPeer(storage);
70
- }
71
-
72
- wss.on("connection", function connection(ws, req) {
73
- // ping/pong for the connection liveness
74
- const pinging = setInterval(() => {
75
- ws.send(
76
- JSON.stringify({
77
- type: "ping",
78
- time: Date.now(),
79
- dc: "unknown",
80
- }),
81
- );
82
- }, 1500);
83
-
84
- ws.on("close", () => {
85
- clearInterval(pinging);
86
- });
87
-
88
- const clientAddress =
89
- (req.headers["x-forwarded-for"] as string | undefined)
90
- ?.split(",")[0]
91
- ?.trim() || req.socket.remoteAddress;
92
-
93
- const clientId = clientAddress + "@" + new Date().toISOString();
94
-
95
- localNode.syncManager.addPeer(
96
- createWebSocketPeer({
97
- id: clientId,
98
- role: "client",
99
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
- websocket: ws as any, // TODO: fix types
101
- expectPings: false,
102
- batchingByDefault: false
103
- }),
104
- );
105
-
106
- ws.on("error", (e) =>
107
- console.error(`Error on connection ${clientId}:`, e),
108
- );
109
- });
110
-
111
- server.on("upgrade", function upgrade(req, socket, head) {
112
- if (req.url !== "/health") {
113
- wss.handleUpgrade(req, socket, head, function done(ws) {
114
- wss.emit("connection", ws, req);
115
- });
116
- }
117
- });
118
-
119
- server.listen(parseInt(port));
120
-
121
- // Keep the server up
122
- yield* Effect.never;
123
- });
124
- },
125
- );