Compare commits

...

7 Commits

Author SHA1 Message Date
Anselm
bb855ed83d Publish
- jazz-example-pets@0.0.13
 - jazz-example-todo@0.0.38
 - cojson@0.3.4
 - cojson-simple-sync@0.3.6
 - cojson-storage-indexeddb@0.3.4
 - cojson-storage-sqlite@0.3.6
 - jazz-browser@0.3.4
 - jazz-browser-auth-local@0.3.4
 - jazz-browser-media-images@0.3.4
 - jazz-react@0.3.4
 - jazz-react-auth-local@0.3.4
 - jazz-react-media-images@0.3.4
2023-09-22 14:33:25 +01:00
Anselm
a8ef49e228 Small lint fixes 2023-09-22 14:32:41 +01:00
Anselm
e0ad32dbd2 Implement exponential falloff, fixes #69 2023-09-22 14:30:55 +01:00
Anselm
62bf769cad Publish
- cojson-simple-sync@0.3.5
 - cojson-storage-sqlite@0.3.5
2023-09-22 10:36:17 +01:00
Anselm
7488ff25b2 Missed one bit of JSON parsing to make more robust 2023-09-22 10:36:02 +01:00
Anselm
b69c9da983 Publish
- cojson-simple-sync@0.3.4
 - cojson-storage-sqlite@0.3.4
2023-09-22 10:25:25 +01:00
Anselm
d30fdef8aa More JSON.parse resiliency in cojson-storage-sqlite 2023-09-22 10:25:08 +01:00
16 changed files with 164 additions and 88 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.12",
"version": "0.0.13",
"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.3",
"jazz-react-auth-local": "^0.3.3",
"jazz-react-media-images": "^0.3.3",
"jazz-react": "^0.3.4",
"jazz-react-auth-local": "^0.3.4",
"jazz-react-media-images": "^0.3.4",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.37",
"version": "0.0.38",
"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.3",
"jazz-react-auth-local": "^0.3.3",
"jazz-react": "^0.3.4",
"jazz-react-auth-local": "^0.3.4",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -4,7 +4,7 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.3.3",
"version": "0.3.6",
"devDependencies": {
"@types/jest": "^29.5.3",
"@types/ws": "^8.5.5",
@@ -16,8 +16,8 @@
"typescript": "5.0.2"
},
"dependencies": {
"cojson": "^0.3.3",
"cojson-storage-sqlite": "^0.3.3",
"cojson": "^0.3.4",
"cojson-storage-sqlite": "^0.3.6",
"ws": "^8.13.0"
},
"scripts": {

View File

@@ -1,11 +1,11 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.3.3",
"version": "0.3.4",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.3.3",
"cojson": "^0.3.4",
"typescript": "^5.1.6"
},
"devDependencies": {

View File

@@ -1,13 +1,13 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.3.3",
"version": "0.3.6",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"better-sqlite3": "^8.5.2",
"cojson": "^0.3.3",
"cojson": "^0.3.4",
"typescript": "^5.1.6"
},
"scripts": {

View File

@@ -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;

View File

@@ -5,7 +5,7 @@
"types": "dist/index.d.ts",
"type": "module",
"license": "MIT",
"version": "0.3.3",
"version": "0.3.4",
"devDependencies": {
"@types/jest": "^29.5.3",
"@typescript-eslint/eslint-plugin": "^6.2.1",

View File

@@ -252,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
);
}
}

View File

@@ -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;
@@ -224,7 +223,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)

View File

@@ -1,11 +1,11 @@
{
"name": "jazz-browser-auth-local",
"version": "0.3.3",
"version": "0.3.4",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"jazz-browser": "^0.3.3",
"jazz-browser": "^0.3.4",
"typescript": "^5.1.6"
},
"scripts": {

View File

@@ -1,13 +1,13 @@
{
"name": "jazz-browser-media-images",
"version": "0.3.3",
"version": "0.3.4",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.3.3",
"cojson": "^0.3.4",
"image-blob-reduce": "^4.1.0",
"jazz-browser": "^0.3.3",
"jazz-browser": "^0.3.4",
"typescript": "^5.1.6"
},
"scripts": {

View File

@@ -1,12 +1,12 @@
{
"name": "jazz-browser",
"version": "0.3.3",
"version": "0.3.4",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.3.3",
"cojson-storage-indexeddb": "^0.3.3",
"cojson": "^0.3.4",
"cojson-storage-indexeddb": "^0.3.4",
"typescript": "^5.1.6"
},
"scripts": {

View File

@@ -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);
@@ -53,15 +62,33 @@ export async function createBrowserNode({
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.syncManager.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,6 +99,7 @@ export async function createBrowserNode({
node,
done: () => {
shouldTryToReconnect = false;
window.removeEventListener("online", onOnline);
console.log("Cleaning up node");
for (const peer of Object.values(node.syncManager.peers)) {
peer.outgoing
@@ -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>(

View File

@@ -1,12 +1,12 @@
{
"name": "jazz-react-auth-local",
"version": "0.3.3",
"version": "0.3.4",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"jazz-browser-auth-local": "^0.3.3",
"jazz-react": "^0.3.3",
"jazz-browser-auth-local": "^0.3.4",
"jazz-react": "^0.3.4",
"typescript": "^5.1.6"
},
"devDependencies": {

View File

@@ -1,14 +1,14 @@
{
"name": "jazz-react-media-images",
"version": "0.3.3",
"version": "0.3.4",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.3.3",
"jazz-browser": "^0.3.3",
"jazz-browser-media-images": "^0.3.3",
"jazz-react": "^0.3.3",
"cojson": "^0.3.4",
"jazz-browser": "^0.3.4",
"jazz-browser-media-images": "^0.3.4",
"jazz-react": "^0.3.4",
"typescript": "^5.1.6"
},
"devDependencies": {

View File

@@ -1,12 +1,12 @@
{
"name": "jazz-react",
"version": "0.3.3",
"version": "0.3.4",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"cojson": "^0.3.3",
"jazz-browser": "^0.3.3",
"cojson": "^0.3.4",
"jazz-browser": "^0.3.4",
"typescript": "^5.1.6"
},
"devDependencies": {