Compare commits
24 Commits
cojson@0.3
...
v0.3.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91e5e7f2ab | ||
|
|
e3f7e2f1bd | ||
|
|
084cf80c60 | ||
|
|
632e3bbb08 | ||
|
|
17d17833b2 | ||
|
|
8e22bd9c1e | ||
|
|
98213743f3 | ||
|
|
bb855ed83d | ||
|
|
a8ef49e228 | ||
|
|
e0ad32dbd2 | ||
|
|
62bf769cad | ||
|
|
7488ff25b2 | ||
|
|
b69c9da983 | ||
|
|
d30fdef8aa | ||
|
|
9c5a6b9833 | ||
|
|
d300d265c4 | ||
|
|
1d72ce587f | ||
|
|
3fdb41dcb9 | ||
|
|
f20de2f04a | ||
|
|
31b31f111b | ||
|
|
2ae9fb9778 | ||
|
|
cd0da0f6bf | ||
|
|
cd9bfbb9fa | ||
|
|
ed0428bf97 |
6
DOCS.md
6
DOCS.md
@@ -7394,12 +7394,12 @@ TODO: document
|
||||
|
||||
----
|
||||
|
||||
## `createImage(image, inGroup)`
|
||||
## `createImage(imageBlobOrFile, inGroup)`
|
||||
|
||||
<sup>(function in `jazz-react-media-images`)</sup>
|
||||
|
||||
```typescript
|
||||
export function createImage(image: Blob | File, inGroup: Group): Promise<Media.ImageDefinition>
|
||||
export function createImage(imageBlobOrFile: Blob | File, inGroup: Group): Promise<Media.ImageDefinition>
|
||||
```
|
||||
TODO: document
|
||||
|
||||
@@ -7407,7 +7407,7 @@ TODO: document
|
||||
|
||||
| name | description |
|
||||
| ----: | ---- |
|
||||
| `image` | TODO: document |
|
||||
| `imageBlobOrFile` | TODO: document |
|
||||
| `inGroup` | TODO: document |
|
||||
|
||||
|
||||
|
||||
@@ -113,4 +113,4 @@ In the future we'll build a dedicated docs page on the Jazz homepage.
|
||||
|
||||
----
|
||||
|
||||
Copyright 2023: Garden Computing, Inc.
|
||||
Copyright 2023 — Garden Computing, Inc.
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.14",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -16,9 +16,9 @@
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "^0.3.0-alpha.0",
|
||||
"jazz-react-auth-local": "^0.3.0-alpha.0",
|
||||
"jazz-react-media-images": "^0.3.0-alpha.0",
|
||||
"jazz-react": "^0.3.5",
|
||||
"jazz-react-auth-local": "^0.3.5",
|
||||
"jazz-react-media-images": "^0.3.5",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.35",
|
||||
"version": "0.0.39",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -16,8 +16,8 @@
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "^0.3.0-alpha.0",
|
||||
"jazz-react-auth-local": "^0.3.0-alpha.0",
|
||||
"jazz-react": "^0.3.5",
|
||||
"jazz-react-auth-local": "^0.3.5",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useParams } from "react-router";
|
||||
/** Walkthrough: Reactively rendering a todo project as a table,
|
||||
* adding and editing tasks
|
||||
*
|
||||
* Here in `<TodoTable/>`, we use `useSyncedData()` for the first time,
|
||||
* Here in `<TodoTable/>`, we use `useSyncedQuery()` for the first time,
|
||||
* in this case to load the CoValue for our `TodoProject` as well as
|
||||
* the `ListOfTasks` referenced in it.
|
||||
*/
|
||||
@@ -32,7 +32,7 @@ import { useParams } from "react-router";
|
||||
export function ProjectTodoTable() {
|
||||
const projectId = useParams<{ projectId: CoID<TodoProject> }>().projectId;
|
||||
|
||||
// `useSyncedData()` reactively subscribes to updates to a CoValue's
|
||||
// `useSyncedQuery()` reactively subscribes to updates to a CoValue's
|
||||
// content - whether we create edits locally, load persisted data, or receive
|
||||
// sync updates from other devices or participants!
|
||||
// It also recursively resolves and subsribes to all referenced CoValues.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"types": "src/index.ts",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"version": "0.3.7",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/ws": "^8.5.5",
|
||||
@@ -16,8 +16,8 @@
|
||||
"typescript": "5.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cojson": "^0.3.0-alpha.0",
|
||||
"cojson-storage-sqlite": "^0.3.0-alpha.0",
|
||||
"cojson": "^0.3.5",
|
||||
"cojson-storage-sqlite": "^0.3.7",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -16,7 +16,7 @@ const localNode = new LocalNode(
|
||||
);
|
||||
|
||||
SQLiteStorage.asPeer({ filename: "./sync.db" })
|
||||
.then((storage) => localNode.sync.addPeer(storage))
|
||||
.then((storage) => localNode.syncManager.addPeer(storage))
|
||||
.catch((e) => console.error(e));
|
||||
|
||||
wss.on("connection", function connection(ws, req) {
|
||||
@@ -34,8 +34,6 @@ wss.on("connection", function connection(ws, req) {
|
||||
clearInterval(pinging);
|
||||
});
|
||||
|
||||
|
||||
|
||||
const clientAddress =
|
||||
(req.headers["x-forwarded-for"] as string | undefined)
|
||||
?.split(",")[0]
|
||||
@@ -43,7 +41,7 @@ wss.on("connection", function connection(ws, req) {
|
||||
|
||||
const clientId = clientAddress + "@" + new Date().toISOString();
|
||||
|
||||
localNode.sync.addPeer({
|
||||
localNode.syncManager.addPeer({
|
||||
id: clientId,
|
||||
role: "client",
|
||||
incoming: websocketReadableStream(ws),
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "cojson-storage-indexeddb",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"version": "0.3.5",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cojson": "^0.3.0-alpha.0",
|
||||
"cojson": "^0.3.5",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -12,7 +12,7 @@ test.skip("Should be able to initialize and load from empty DB", async () => {
|
||||
)
|
||||
);
|
||||
|
||||
node.sync.addPeer(await IDBStorage.asPeer({ trace: true }));
|
||||
node.syncManager.addPeer(await IDBStorage.asPeer({ trace: true }));
|
||||
|
||||
console.log("yay!");
|
||||
|
||||
@@ -20,7 +20,7 @@ test.skip("Should be able to initialize and load from empty DB", async () => {
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(node.sync.peers["storage"]).toBeDefined();
|
||||
expect(node.syncManager.peers["storage"]).toBeDefined();
|
||||
});
|
||||
|
||||
test("Should be able to sync data to database and then load that from a new node", async () => {
|
||||
@@ -33,7 +33,7 @@ test("Should be able to sync data to database and then load that from a new node
|
||||
)
|
||||
);
|
||||
|
||||
node1.sync.addPeer(
|
||||
node1.syncManager.addPeer(
|
||||
await IDBStorage.asPeer({ trace: true, localNodeName: "node1" })
|
||||
);
|
||||
|
||||
@@ -56,7 +56,7 @@ test("Should be able to sync data to database and then load that from a new node
|
||||
)
|
||||
);
|
||||
|
||||
node2.sync.addPeer(
|
||||
node2.syncManager.addPeer(
|
||||
await IDBStorage.asPeer({ trace: true, localNodeName: "node2" })
|
||||
);
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "cojson-storage-sqlite",
|
||||
"type": "module",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"version": "0.3.7",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^8.5.2",
|
||||
"cojson": "^0.3.0-alpha.0",
|
||||
"cojson": "^0.3.5",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -237,19 +237,31 @@ export class SQLiteStorage {
|
||||
sessions: {},
|
||||
};
|
||||
|
||||
const parsedHeader = (coValueRow?.header &&
|
||||
JSON.parse(coValueRow.header)) as
|
||||
| CojsonInternalTypes.CoValueHeader
|
||||
| undefined;
|
||||
let parsedHeader;
|
||||
|
||||
const newContentPieces: CojsonInternalTypes.NewContentMessage[] = [
|
||||
{
|
||||
action: "content",
|
||||
id: theirKnown.id,
|
||||
header: theirKnown.header ? undefined : parsedHeader,
|
||||
new: {},
|
||||
},
|
||||
];
|
||||
try {
|
||||
parsedHeader = (coValueRow?.header &&
|
||||
JSON.parse(coValueRow.header)) as
|
||||
| CojsonInternalTypes.CoValueHeader
|
||||
| undefined;
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
theirKnown.id,
|
||||
"Invalid JSON in header",
|
||||
e,
|
||||
coValueRow?.header
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const newContentPieces: CojsonInternalTypes.NewContentMessage[] = [
|
||||
{
|
||||
action: "content",
|
||||
id: theirKnown.id,
|
||||
header: theirKnown.header ? undefined : parsedHeader,
|
||||
new: {},
|
||||
},
|
||||
];
|
||||
|
||||
for (const sessionRow of allOurSessions) {
|
||||
ourKnown.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
|
||||
@@ -265,7 +277,10 @@ export class SQLiteStorage {
|
||||
.prepare<[number, number]>(
|
||||
`SELECT * FROM signatureAfter WHERE ses = ? AND idx >= ?`
|
||||
)
|
||||
.all(sessionRow.rowID, firstNewTxIdx) as SignatureAfterRow[];
|
||||
.all(
|
||||
sessionRow.rowID,
|
||||
firstNewTxIdx
|
||||
) as SignatureAfterRow[];
|
||||
|
||||
// console.log(
|
||||
// theirKnown.id,
|
||||
@@ -295,7 +310,8 @@ export class SQLiteStorage {
|
||||
if (!sessionEntry) {
|
||||
sessionEntry = {
|
||||
after: idx,
|
||||
lastSignature: "WILL_BE_REPLACED" as CojsonInternalTypes.Signature,
|
||||
lastSignature:
|
||||
"WILL_BE_REPLACED" as CojsonInternalTypes.Signature,
|
||||
newTransactions: [],
|
||||
};
|
||||
newContentPieces[newContentPieces.length - 1]!.new[
|
||||
@@ -303,7 +319,21 @@ export class SQLiteStorage {
|
||||
] = sessionEntry;
|
||||
}
|
||||
|
||||
sessionEntry.newTransactions.push(JSON.parse(tx.tx));
|
||||
let parsedTx;
|
||||
|
||||
try {
|
||||
parsedTx = JSON.parse(tx.tx);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
theirKnown.id,
|
||||
"Invalid JSON in transaction",
|
||||
e,
|
||||
tx.tx
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
sessionEntry.newTransactions.push(parsedTx);
|
||||
|
||||
if (
|
||||
signaturesAndIdxs[0] &&
|
||||
@@ -331,28 +361,46 @@ export class SQLiteStorage {
|
||||
const dependedOnCoValues =
|
||||
parsedHeader?.ruleset.type === "group"
|
||||
? newContentPieces
|
||||
.flatMap((piece) => Object.values(piece.new)).flatMap((sessionEntry) =>
|
||||
sessionEntry.newTransactions.flatMap((tx) => {
|
||||
if (tx.privacy !== "trusting") return [];
|
||||
// TODO: avoid parsing here?
|
||||
return cojsonInternals
|
||||
.parseJSON(tx.changes)
|
||||
.map(
|
||||
(change) =>
|
||||
change &&
|
||||
typeof change === "object" &&
|
||||
"op" in change &&
|
||||
change.op === "set" &&
|
||||
"key" in change &&
|
||||
change.key
|
||||
)
|
||||
.filter(
|
||||
(key): key is CojsonInternalTypes.RawCoID =>
|
||||
typeof key === "string" &&
|
||||
key.startsWith("co_")
|
||||
);
|
||||
})
|
||||
)
|
||||
.flatMap((piece) => Object.values(piece.new))
|
||||
.flatMap((sessionEntry) =>
|
||||
sessionEntry.newTransactions.flatMap((tx) => {
|
||||
if (tx.privacy !== "trusting") return [];
|
||||
// TODO: avoid parsing here?
|
||||
let parsedChanges;
|
||||
|
||||
try {
|
||||
parsedChanges = cojsonInternals.parseJSON(
|
||||
tx.changes
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
theirKnown.id,
|
||||
"Invalid JSON in transaction",
|
||||
e,
|
||||
tx.changes
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return parsedChanges
|
||||
.map(
|
||||
(change) =>
|
||||
change &&
|
||||
typeof change === "object" &&
|
||||
"op" in change &&
|
||||
change.op === "set" &&
|
||||
"key" in change &&
|
||||
change.key
|
||||
)
|
||||
.filter(
|
||||
(
|
||||
key
|
||||
): key is CojsonInternalTypes.RawCoID =>
|
||||
typeof key === "string" &&
|
||||
key.startsWith("co_")
|
||||
);
|
||||
})
|
||||
)
|
||||
: parsedHeader?.ruleset.type === "ownedByGroup"
|
||||
? [parsedHeader?.ruleset.group]
|
||||
: [];
|
||||
@@ -499,7 +547,7 @@ export class SQLiteStorage {
|
||||
sessionUpdate.sessionID,
|
||||
sessionUpdate.lastIdx,
|
||||
sessionUpdate.lastSignature,
|
||||
sessionUpdate.bytesSinceLastSignature,
|
||||
sessionUpdate.bytesSinceLastSignature
|
||||
) as { rowID: number };
|
||||
|
||||
const sessionRowID = upsertedSession.rowID;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"version": "0.3.7",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
||||
|
||||
@@ -22,23 +22,27 @@ let blake3incrementalUpdateSLOW_WITH_DEVTOOLS: (
|
||||
let blake3digestForState: (state: Uint8Array) => Uint8Array;
|
||||
|
||||
export const cryptoReady = new Promise<void>((resolve) => {
|
||||
createBLAKE3().then((bl3) => {
|
||||
blake3Instance = bl3;
|
||||
blake3HashOnce = (data) => {
|
||||
return bl3.init().update(data).digest("binary");
|
||||
};
|
||||
blake3HashOnceWithContext = (data, { context }) => {
|
||||
return bl3.init().update(context).update(data).digest("binary");
|
||||
};
|
||||
blake3incrementalUpdateSLOW_WITH_DEVTOOLS = (state, data) => {
|
||||
bl3.load(state).update(data);
|
||||
return bl3.save();
|
||||
};
|
||||
blake3digestForState = (state) => {
|
||||
return bl3.load(state).digest("binary");
|
||||
};
|
||||
resolve();
|
||||
});
|
||||
createBLAKE3()
|
||||
.then((bl3) => {
|
||||
blake3Instance = bl3;
|
||||
blake3HashOnce = (data) => {
|
||||
return bl3.init().update(data).digest("binary");
|
||||
};
|
||||
blake3HashOnceWithContext = (data, { context }) => {
|
||||
return bl3.init().update(context).update(data).digest("binary");
|
||||
};
|
||||
blake3incrementalUpdateSLOW_WITH_DEVTOOLS = (state, data) => {
|
||||
bl3.load(state).update(data);
|
||||
return bl3.save();
|
||||
};
|
||||
blake3digestForState = (state) => {
|
||||
return bl3.load(state).digest("binary");
|
||||
};
|
||||
resolve();
|
||||
})
|
||||
.catch((e) =>
|
||||
console.error("Failed to load cryptography dependencies", e)
|
||||
);
|
||||
});
|
||||
|
||||
export type SignerSecret = `signerSecret_z${string}`;
|
||||
|
||||
@@ -73,7 +73,24 @@ export function determineValidTransactions(
|
||||
// console.log("before", { memberState, validTransactions });
|
||||
const transactor = accountOrAgentIDfromSessionID(sessionID);
|
||||
|
||||
const changes = parseJSON(tx.changes);
|
||||
let changes;
|
||||
|
||||
try {
|
||||
changes = parseJSON(tx.changes);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
coValue.id,
|
||||
"Invalid JSON in transaction",
|
||||
e,
|
||||
tx,
|
||||
JSON.stringify(tx.changes, (k, v) =>
|
||||
k === "changes" || k === "encryptedChanges"
|
||||
? v.slice(0, 20) + "..."
|
||||
: v
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const change = changes[0] as
|
||||
| MapOpPayload<AccountID | AgentID, Role>
|
||||
@@ -235,7 +252,7 @@ export function determineValidTransactions(
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Unknown ruleset type " + (coValue.header.ruleset as any).type
|
||||
"Unknown ruleset type " + (coValue.header.ruleset as {type: string}).type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
WritableStreamDefaultWriter,
|
||||
} from "isomorphic-streams";
|
||||
import { RawCoID, SessionID } from "./ids.js";
|
||||
import { stableStringify } from "./jsonStringify.js";
|
||||
|
||||
export type CoValueKnownState = {
|
||||
id: RawCoID;
|
||||
@@ -67,6 +66,7 @@ export interface Peer {
|
||||
incoming: ReadableStream<SyncMessage>;
|
||||
outgoing: WritableStream<SyncMessage>;
|
||||
role: "peer" | "server" | "client";
|
||||
delayOnError?: number;
|
||||
}
|
||||
|
||||
export interface PeerState {
|
||||
@@ -76,6 +76,7 @@ export interface PeerState {
|
||||
incoming: ReadableStream<SyncMessage>;
|
||||
outgoing: WritableStreamDefaultWriter<SyncMessage>;
|
||||
role: "peer" | "server" | "client";
|
||||
delayOnError?: number;
|
||||
}
|
||||
|
||||
export function combinedKnownStates(
|
||||
@@ -224,7 +225,7 @@ export class SyncManager {
|
||||
peer.optimisticKnownStates[id] || emptyKnownState(id);
|
||||
|
||||
const sendPieces = async () => {
|
||||
for (const [i, piece] of newContentPieces.entries()) {
|
||||
for (const [_i, piece] of newContentPieces.entries()) {
|
||||
// console.log(
|
||||
// `${id} -> ${peer.id}: Sending content piece ${i + 1}/${newContentPieces.length} header: ${!!piece.header}`,
|
||||
// // Object.values(piece.new).map((s) => s.newTransactions)
|
||||
@@ -254,6 +255,7 @@ export class SyncManager {
|
||||
outgoing: peer.outgoing.getWriter(),
|
||||
toldKnownState: new Set(),
|
||||
role: peer.role,
|
||||
delayOnError: peer.delayOnError,
|
||||
};
|
||||
this.peers[peer.id] = peerState;
|
||||
|
||||
@@ -284,6 +286,7 @@ export class SyncManager {
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(
|
||||
new Date(),
|
||||
`Error reading from peer ${peer.id}, handling msg`,
|
||||
JSON.stringify(msg, (k, v) =>
|
||||
k === "changes" || k === "encryptedChanges"
|
||||
@@ -292,6 +295,11 @@ export class SyncManager {
|
||||
),
|
||||
e
|
||||
);
|
||||
if (peerState.delayOnError) {
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, peerState.delayOnError);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -505,7 +513,11 @@ export class SyncManager {
|
||||
console.error(
|
||||
"Failed to add transactions",
|
||||
msg.id,
|
||||
newTransactions
|
||||
JSON.stringify(newTransactions, (k, v) =>
|
||||
k === "changes" || k === "encryptedChanges"
|
||||
? v.slice(0, 20) + "..."
|
||||
: v
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "jazz-browser-auth-local",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"version": "0.3.5",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jazz-browser": "^0.3.0-alpha.0",
|
||||
"jazz-browser": "^0.3.5",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -72,7 +72,7 @@ export class BrowserLocalAuth implements AuthProvider {
|
||||
this.appHostname
|
||||
);
|
||||
for (const peer of initialPeers) {
|
||||
node.sync.addPeer(peer);
|
||||
node.syncManager.addPeer(peer);
|
||||
}
|
||||
doneSigningUpOrLoggingIn(node);
|
||||
this.driver.onSignedIn({ logOut });
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "jazz-browser-media-images",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"version": "0.3.5",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cojson": "^0.3.0-alpha.0",
|
||||
"cojson": "^0.3.5",
|
||||
"image-blob-reduce": "^4.1.0",
|
||||
"jazz-browser": "^0.3.0-alpha.0",
|
||||
"jazz-browser": "^0.3.5",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "jazz-browser",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"version": "0.3.5",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cojson": "^0.3.0-alpha.0",
|
||||
"cojson-storage-indexeddb": "^0.3.0-alpha.0",
|
||||
"cojson": "^0.3.5",
|
||||
"cojson-storage-indexeddb": "^0.3.5",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -25,7 +25,7 @@ export type BrowserNodeHandle = {
|
||||
export async function createBrowserNode({
|
||||
auth,
|
||||
syncAddress = "wss://sync.jazz.tools",
|
||||
reconnectionTimeout = 300,
|
||||
reconnectionTimeout: initialReconnectionTimeout = 500,
|
||||
}: {
|
||||
auth: AuthProvider;
|
||||
syncAddress?: string;
|
||||
@@ -37,6 +37,15 @@ export async function createBrowserNode({
|
||||
const firstWsPeer = createWebSocketPeer(syncAddress);
|
||||
let shouldTryToReconnect = true;
|
||||
|
||||
let currentReconnectionTimeout = initialReconnectionTimeout;
|
||||
|
||||
function onOnline() {
|
||||
console.log("Online, resetting reconnection timeout");
|
||||
currentReconnectionTimeout = initialReconnectionTimeout;
|
||||
}
|
||||
|
||||
window.addEventListener("online", onOnline);
|
||||
|
||||
const node = await auth.createNode(
|
||||
(accountID) => {
|
||||
const sessionHandle = getSessionHandleFor(accountID);
|
||||
@@ -49,19 +58,37 @@ export async function createBrowserNode({
|
||||
async function websocketReconnectLoop() {
|
||||
while (shouldTryToReconnect) {
|
||||
if (
|
||||
Object.keys(node.sync.peers).some((peerId) =>
|
||||
Object.keys(node.syncManager.peers).some((peerId) =>
|
||||
peerId.includes(syncAddress)
|
||||
)
|
||||
) {
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, reconnectionTimeout)
|
||||
);
|
||||
// TODO: this might drain battery, use listeners instead
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
} else {
|
||||
console.log("Websocket disconnected, trying to reconnect");
|
||||
node.sync.addPeer(createWebSocketPeer(syncAddress));
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, reconnectionTimeout)
|
||||
console.log(
|
||||
"Websocket disconnected, trying to reconnect in " +
|
||||
currentReconnectionTimeout +
|
||||
"ms"
|
||||
);
|
||||
currentReconnectionTimeout = Math.min(
|
||||
currentReconnectionTimeout * 2,
|
||||
30000
|
||||
);
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, currentReconnectionTimeout);
|
||||
window.addEventListener(
|
||||
"online",
|
||||
() => {
|
||||
console.log(
|
||||
"Online, trying to reconnect immediately"
|
||||
);
|
||||
resolve();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
|
||||
node.syncManager.addPeer(createWebSocketPeer(syncAddress));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,8 +99,9 @@ export async function createBrowserNode({
|
||||
node,
|
||||
done: () => {
|
||||
shouldTryToReconnect = false;
|
||||
window.removeEventListener("online", onOnline);
|
||||
console.log("Cleaning up node");
|
||||
for (const peer of Object.values(node.sync.peers)) {
|
||||
for (const peer of Object.values(node.syncManager.peers)) {
|
||||
peer.outgoing
|
||||
.close()
|
||||
.catch((e) => console.error("Error while closing peer", e));
|
||||
@@ -292,13 +320,13 @@ function websocketWritableStream<T>(ws: WebSocket) {
|
||||
}
|
||||
|
||||
export function createInviteLink<T extends CoValue>(
|
||||
value: T | {id: CoID<T>, core: CoValueCore},
|
||||
value: T | { id: CoID<T>; core: CoValueCore },
|
||||
role: "reader" | "writer" | "admin",
|
||||
// default to same address as window.location, but without hash
|
||||
{
|
||||
baseURL = window.location.href.replace(/#.*$/, ""),
|
||||
valueHint
|
||||
}: { baseURL?: string, valueHint?: string } = {}
|
||||
valueHint,
|
||||
}: { baseURL?: string; valueHint?: string } = {}
|
||||
): string {
|
||||
const coValueCore = value.core;
|
||||
const node = coValueCore.node;
|
||||
@@ -319,7 +347,9 @@ export function createInviteLink<T extends CoValue>(
|
||||
|
||||
const inviteSecret = group.createInvite(role);
|
||||
|
||||
return `${baseURL}#/invite/${valueHint ? valueHint + "/" : ""}${value.id}/${inviteSecret}`;
|
||||
return `${baseURL}#/invite/${valueHint ? valueHint + "/" : ""}${
|
||||
value.id
|
||||
}/${inviteSecret}`;
|
||||
}
|
||||
|
||||
export function parseInviteLink<C extends CoValue>(
|
||||
@@ -353,7 +383,6 @@ export function parseInviteLink<C extends CoValue>(
|
||||
}
|
||||
return { valueID, inviteSecret, valueHint };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function consumeInviteLinkFromWindowLocation<C extends CoValue>(
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "jazz-react-auth-local",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"version": "0.3.5",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jazz-browser-auth-local": "^0.3.0-alpha.0",
|
||||
"jazz-react": "^0.3.0-alpha.0",
|
||||
"jazz-browser-auth-local": "^0.3.5",
|
||||
"jazz-react": "^0.3.5",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "jazz-react-media-images",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"version": "0.3.5",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cojson": "^0.3.0-alpha.0",
|
||||
"jazz-browser": "^0.3.0-alpha.0",
|
||||
"jazz-browser-media-images": "^0.3.0-alpha.0",
|
||||
"jazz-react": "^0.3.0-alpha.0",
|
||||
"cojson": "^0.3.5",
|
||||
"jazz-browser": "^0.3.5",
|
||||
"jazz-browser-media-images": "^0.3.5",
|
||||
"jazz-react": "^0.3.5",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "jazz-react",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"version": "0.3.5",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cojson": "^0.3.0-alpha.0",
|
||||
"jazz-browser": "^0.3.0-alpha.0",
|
||||
"cojson": "^0.3.5",
|
||||
"jazz-browser": "^0.3.5",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Reference in New Issue
Block a user