jazz-run 0.8.11 → 0.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/createWorkerAccount.js +80 -0
- package/dist/createWorkerAccount.js.map +1 -0
- package/dist/index.js +36 -6
- package/dist/index.js.map +1 -1
- package/dist/startSyncServer.js +60 -0
- package/dist/startSyncServer.js.map +1 -0
- package/dist/test/createWorkerAccount.test.js +25 -0
- package/dist/test/createWorkerAccount.test.js.map +1 -0
- package/package.json +5 -5
- package/src/createWorkerAccount.ts +114 -0
- package/src/index.ts +74 -6
- package/src/startSyncServer.ts +95 -0
- package/src/test/createWorkerAccount.test.ts +32 -0
- package/dist/accountCreate.js +0 -88
- package/dist/accountCreate.js.map +0 -1
- package/dist/startSync.js +0 -76
- package/dist/startSync.js.map +0 -1
- package/src/accountCreate.ts +0 -125
- package/src/startSync.ts +0 -125
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 {
|
7
|
-
import {
|
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
|
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.
|
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;
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
@@ -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":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,EAClC,IAAI,EACJ,QAAQ,EACR,EAAE,GAKL,EAAE,EAAE;IACD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC;IAEzC,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACrC,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YACxB,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACnB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;IACL,CAAC,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpD,MAAM,WAAW,GAAG,MAAM,CAAC,oBAAoB,EAAE,CAAC;IAClD,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAE/C,MAAM,SAAS,GAAG,IAAI,SAAS,CAC3B,IAAI,eAAe,CAAC,WAAW,EAAE,MAAM,CAAC,EACxC,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAClC,MAAM,CACT,CAAC;IAEF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACZ,MAAM,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAE7C,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QAE7D,SAAS,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,SAAS,UAAU,CAAC,EAAE,EAAE,GAAG;QAC5C,wCAAwC;QACxC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;YAC7B,EAAE,CAAC,IAAI,CACH,IAAI,CAAC,SAAS,CAAC;gBACX,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;gBAChB,EAAE,EAAE,SAAS;aAChB,CAAC,CACL,CAAC;QACN,CAAC,EAAE,IAAI,CAAC,CAAC;QAET,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAChB,aAAa,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,MAAM,aAAa,GACd,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAwB;YAClD,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACf,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC;QAE7C,MAAM,QAAQ,GAAG,aAAa,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAEhE,SAAS,CAAC,WAAW,CAAC,OAAO,CACzB,mBAAmB,CAAC;YAChB,EAAE,EAAE,QAAQ;YACZ,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,EAAE;YACb,WAAW,EAAE,KAAK;YAClB,iBAAiB,EAAE,KAAK;SAC3B,CAAC,CACL,CAAC;QAEF,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CACjB,OAAO,CAAC,KAAK,CAAC,uBAAuB,QAAQ,GAAG,EAAE,CAAC,CAAC,CACvD,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI;QACnD,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YACxB,GAAG,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,IAAI,CAAC,EAAE;gBACjD,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;YACpC,CAAC,CAAC,CAAC;QACP,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAEjD,OAAO,MAAM,CAAC;AAClB,CAAC,CAAC"}
|
@@ -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.
|
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.
|
15
|
-
"cojson-storage-sqlite": "0.8.
|
16
|
-
"cojson-transport-ws": "0.8.
|
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.
|
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 {
|
7
|
-
import {
|
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
|
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.
|
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
|
+
});
|
package/dist/accountCreate.js
DELETED
@@ -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
|
package/dist/startSync.js.map
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"startSync.js","sourceRoot":"","sources":["../src/startSync.ts"],"names":[],"mappings":"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"}
|
package/src/accountCreate.ts
DELETED
@@ -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
|
-
);
|