mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Various sync & time travel fixes
This commit is contained in:
parent
6a17541713
commit
1abb901ec2
5 changed files with 46 additions and 37 deletions
|
@ -139,6 +139,7 @@ import type { ElementsChange } from "../packages/excalidraw/change";
|
|||
|
||||
import Slider from "rc-slider";
|
||||
import "rc-slider/assets/index.css";
|
||||
import { SyncClient } from "../packages/excalidraw/sync/client";
|
||||
|
||||
polyfill();
|
||||
|
||||
|
@ -388,7 +389,7 @@ const ExcalidrawWrapper = () => {
|
|||
syncAPI?.connect();
|
||||
|
||||
return () => {
|
||||
syncAPI?.disconnect();
|
||||
syncAPI?.disconnect(SyncClient.NORMAL_CLOSURE);
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [syncAPI]);
|
||||
|
@ -885,9 +886,11 @@ const ExcalidrawWrapper = () => {
|
|||
max={acknowledgedIncrements.length}
|
||||
value={nextVersion === -1 ? acknowledgedIncrements.length : nextVersion}
|
||||
onChange={(value) => {
|
||||
if (value !== acknowledgedIncrements.length - 1) {
|
||||
// CFDO: should be disabled when offline! (later we could have speculative changes in the versioning log as well)
|
||||
// CFDO: in safari the whole canvas gets selected when dragging
|
||||
if (value !== acknowledgedIncrements.length) {
|
||||
// don't listen to updates in the detached mode
|
||||
syncAPI?.disconnect();
|
||||
syncAPI?.disconnect(SyncClient.NORMAL_CLOSURE);
|
||||
} else {
|
||||
// reconnect once we're back to the latest version
|
||||
syncAPI?.connect();
|
||||
|
|
|
@ -8,7 +8,7 @@ import type {
|
|||
export class DurableIncrementsRepository implements IncrementsRepository {
|
||||
constructor(private storage: DurableObjectStorage) {
|
||||
// #region DEV ONLY
|
||||
this.storage.sql.exec(`DROP TABLE IF EXISTS increments;`);
|
||||
// this.storage.sql.exec(`DROP TABLE IF EXISTS increments;`);
|
||||
// #endregion
|
||||
|
||||
this.storage.sql.exec(`CREATE TABLE IF NOT EXISTS increments(
|
||||
|
|
|
@ -106,7 +106,6 @@
|
|||
"@testing-library/jest-dom": "5.16.2",
|
||||
"@testing-library/react": "16.0.0",
|
||||
"@types/async-lock": "^1.4.2",
|
||||
"@types/lodash.debounce": "4.0.9",
|
||||
"@types/pako": "1.0.3",
|
||||
"@types/pica": "5.1.3",
|
||||
"@types/resize-observer-browser": "0.1.7",
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* eslint-disable no-console */
|
||||
import debounce from "lodash.debounce";
|
||||
import throttle from "lodash.throttle";
|
||||
import ReconnectingWebSocket, {
|
||||
type Event,
|
||||
|
@ -19,11 +18,18 @@ import type {
|
|||
PUSH_PAYLOAD,
|
||||
SERVER_INCREMENT,
|
||||
} from "./protocol";
|
||||
import { debounce } from "../utils";
|
||||
|
||||
const NO_STATUS_RECEIVED_ERROR_CODE = 1005;
|
||||
const ABNORMAL_CLOSURE_ERROR_CODE = 1006;
|
||||
interface AcknowledgedIncrement {
|
||||
increment: StoreIncrement;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export class SyncClient {
|
||||
public static readonly NORMAL_CLOSURE = 1000;
|
||||
public static readonly NO_STATUS_RECEIVED_ERROR_CODE = 1005;
|
||||
public static readonly ABNORMAL_CLOSURE_ERROR_CODE = 1006;
|
||||
|
||||
private static readonly HOST_URL = import.meta.env.DEV
|
||||
? "ws://localhost:8787"
|
||||
: "https://excalidraw-sync.marcel-529.workers.dev";
|
||||
|
@ -38,11 +44,15 @@ export class SyncClient {
|
|||
private readonly repository: MetadataRepository;
|
||||
|
||||
// CFDO: shouldn't be stateful, only request / response
|
||||
private readonly acknowledgedIncrementsMap: Map<string, StoreIncrement> =
|
||||
new Map();
|
||||
private readonly acknowledgedIncrementsMap: Map<
|
||||
string,
|
||||
AcknowledgedIncrement
|
||||
> = new Map();
|
||||
|
||||
public get acknowledgedIncrements() {
|
||||
return Array.from(this.acknowledgedIncrementsMap.values());
|
||||
return Array.from(this.acknowledgedIncrementsMap.values())
|
||||
.sort((a, b) => (a.version < b.version ? -1 : 1))
|
||||
.map((x) => x.increment);
|
||||
}
|
||||
|
||||
private readonly roomId: string;
|
||||
|
@ -87,7 +97,6 @@ export class SyncClient {
|
|||
});
|
||||
}
|
||||
|
||||
// CFDO I: throttle does not work that well here (after some period it tries to reconnect too often)
|
||||
public connect = throttle(
|
||||
() => {
|
||||
if (this.server && this.server.readyState !== this.server.CLOSED) {
|
||||
|
@ -121,7 +130,7 @@ export class SyncClient {
|
|||
);
|
||||
|
||||
public disconnect = throttle(
|
||||
(code?: number, reason?: string) => {
|
||||
(code: number, reason?: string) => {
|
||||
if (!this.server) {
|
||||
return;
|
||||
}
|
||||
|
@ -134,7 +143,9 @@ export class SyncClient {
|
|||
}
|
||||
|
||||
console.log(
|
||||
`Disconnecting from the sync server with code "${code}" and reason "${reason}"...`,
|
||||
`Disconnecting from the sync server with code "${code}"${
|
||||
reason ? ` and reason "${reason}".` : "."
|
||||
}`,
|
||||
);
|
||||
this.server.removeEventListener("message", this.onMessage);
|
||||
this.server.removeEventListener("open", this.onOpen);
|
||||
|
@ -153,7 +164,7 @@ export class SyncClient {
|
|||
|
||||
private onClose = (event: CloseEvent) => {
|
||||
this.disconnect(
|
||||
event.code || NO_STATUS_RECEIVED_ERROR_CODE,
|
||||
event.code || SyncClient.NO_STATUS_RECEIVED_ERROR_CODE,
|
||||
event.reason || "Connection closed without a reason",
|
||||
);
|
||||
};
|
||||
|
@ -161,8 +172,8 @@ export class SyncClient {
|
|||
private onError = (event: Event) => {
|
||||
this.disconnect(
|
||||
event.type === "error"
|
||||
? ABNORMAL_CLOSURE_ERROR_CODE
|
||||
: NO_STATUS_RECEIVED_ERROR_CODE,
|
||||
? SyncClient.ABNORMAL_CLOSURE_ERROR_CODE
|
||||
: SyncClient.NO_STATUS_RECEIVED_ERROR_CODE,
|
||||
`Received "${event.type}" on the sync connection`,
|
||||
);
|
||||
};
|
||||
|
@ -227,12 +238,9 @@ export class SyncClient {
|
|||
});
|
||||
}
|
||||
|
||||
// CFDO: should be flushed once regular push / pull goes through
|
||||
private schedulePush = (ms: number = 1000) =>
|
||||
debounce(this.push, ms, { leading: true, trailing: false });
|
||||
|
||||
private schedulePull = (ms: number = 1000) =>
|
||||
debounce(this.pull, ms, { leading: true, trailing: false });
|
||||
// CFDO: could be flushed once regular push / pull goes through
|
||||
private schedulePush = debounce(() => this.push(), 1000);
|
||||
private schedulePull = debounce(() => this.pull(), 1000);
|
||||
|
||||
// CFDO: refactor by applying all operations to store, not to the elements
|
||||
private handleAcknowledged(payload: { increments: Array<SERVER_INCREMENT> }) {
|
||||
|
@ -246,11 +254,12 @@ export class SyncClient {
|
|||
const { increments: remoteIncrements } = payload;
|
||||
|
||||
// apply remote increments
|
||||
for (const { id, version, payload } of remoteIncrements.sort((a, b) =>
|
||||
a.version <= b.version ? -1 : 1,
|
||||
)) {
|
||||
for (const { id, version, payload } of remoteIncrements) {
|
||||
// CFDO: temporary to load all increments on init
|
||||
this.acknowledgedIncrementsMap.set(id, StoreIncrement.load(payload));
|
||||
this.acknowledgedIncrementsMap.set(id, {
|
||||
increment: StoreIncrement.load(payload),
|
||||
version,
|
||||
});
|
||||
|
||||
// local increment shall not have to be applied again
|
||||
if (this.queue.has(id)) {
|
||||
|
@ -301,11 +310,11 @@ export class SyncClient {
|
|||
this.lastAcknowledgedVersion = nextAcknowledgedVersion;
|
||||
} catch (e) {
|
||||
console.error("Failed to apply acknowledged increments:", e);
|
||||
this.schedulePull().call(this);
|
||||
this.schedulePull();
|
||||
return;
|
||||
}
|
||||
|
||||
this.schedulePush().call(this);
|
||||
this.schedulePush();
|
||||
}
|
||||
|
||||
private handleRejected(payload: { ids: Array<string>; message: string }) {
|
||||
|
|
14
yarn.lock
14
yarn.lock
|
@ -3383,13 +3383,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
||||
|
||||
"@types/lodash.debounce@4.0.9":
|
||||
version "4.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz#0f5f21c507bce7521b5e30e7a24440975ac860a5"
|
||||
integrity sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash.throttle@4.1.7":
|
||||
version "4.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash.throttle/-/lodash.throttle-4.1.7.tgz#4ef379eb4f778068022310ef166625f420b6ba58"
|
||||
|
@ -7971,7 +7964,7 @@ lodash.camelcase@^4.3.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
|
||||
|
||||
lodash.debounce@4.0.8, lodash.debounce@^4.0.8:
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
|
||||
|
@ -9409,6 +9402,11 @@ rechoir@^0.7.0:
|
|||
dependencies:
|
||||
resolve "^1.9.0"
|
||||
|
||||
reconnecting-websocket@4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783"
|
||||
integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==
|
||||
|
||||
redent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue