@abraca/dabra 0.1.0
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/dist/hocuspocus-provider.cjs +3237 -0
- package/dist/hocuspocus-provider.cjs.map +1 -0
- package/dist/hocuspocus-provider.esm.js +3199 -0
- package/dist/hocuspocus-provider.esm.js.map +1 -0
- package/dist/index.d.ts +784 -0
- package/package.json +42 -0
- package/src/AbracadabraProvider.ts +381 -0
- package/src/CryptoIdentityKeystore.ts +294 -0
- package/src/EventEmitter.ts +44 -0
- package/src/HocuspocusProvider.ts +603 -0
- package/src/HocuspocusProviderWebsocket.ts +533 -0
- package/src/IncomingMessage.ts +63 -0
- package/src/MessageReceiver.ts +139 -0
- package/src/MessageSender.ts +22 -0
- package/src/OfflineStore.ts +185 -0
- package/src/OutgoingMessage.ts +25 -0
- package/src/OutgoingMessages/AuthenticationMessage.ts +25 -0
- package/src/OutgoingMessages/AwarenessMessage.ts +41 -0
- package/src/OutgoingMessages/CloseMessage.ts +17 -0
- package/src/OutgoingMessages/QueryAwarenessMessage.ts +17 -0
- package/src/OutgoingMessages/StatelessMessage.ts +18 -0
- package/src/OutgoingMessages/SubdocMessage.ts +35 -0
- package/src/OutgoingMessages/SyncStepOneMessage.ts +25 -0
- package/src/OutgoingMessages/SyncStepTwoMessage.ts +25 -0
- package/src/OutgoingMessages/UpdateMessage.ts +20 -0
- package/src/index.ts +7 -0
- package/src/types.ts +144 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { readAuthMessage } from "@hocuspocus/common";
|
|
2
|
+
import { readVarInt, readVarString } from "lib0/decoding";
|
|
3
|
+
import type { CloseEvent } from "ws";
|
|
4
|
+
import * as awarenessProtocol from "y-protocols/awareness";
|
|
5
|
+
import { messageYjsSyncStep2, readSyncMessage } from "y-protocols/sync";
|
|
6
|
+
import type { HocuspocusProvider } from "./HocuspocusProvider.ts";
|
|
7
|
+
import type { IncomingMessage } from "./IncomingMessage.ts";
|
|
8
|
+
import { OutgoingMessage } from "./OutgoingMessage.ts";
|
|
9
|
+
import { MessageType } from "./types.ts";
|
|
10
|
+
|
|
11
|
+
export class MessageReceiver {
|
|
12
|
+
message: IncomingMessage;
|
|
13
|
+
|
|
14
|
+
constructor(message: IncomingMessage) {
|
|
15
|
+
this.message = message;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public apply(provider: HocuspocusProvider, emitSynced: boolean) {
|
|
19
|
+
const { message } = this;
|
|
20
|
+
const type = message.readVarUint();
|
|
21
|
+
|
|
22
|
+
const emptyMessageLength = message.length();
|
|
23
|
+
|
|
24
|
+
switch (type) {
|
|
25
|
+
case MessageType.Sync:
|
|
26
|
+
this.applySyncMessage(provider, emitSynced);
|
|
27
|
+
break;
|
|
28
|
+
|
|
29
|
+
case MessageType.Awareness:
|
|
30
|
+
this.applyAwarenessMessage(provider);
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
case MessageType.Auth:
|
|
34
|
+
this.applyAuthMessage(provider);
|
|
35
|
+
break;
|
|
36
|
+
|
|
37
|
+
case MessageType.QueryAwareness:
|
|
38
|
+
this.applyQueryAwarenessMessage(provider);
|
|
39
|
+
break;
|
|
40
|
+
|
|
41
|
+
case MessageType.Stateless:
|
|
42
|
+
provider.receiveStateless(readVarString(message.decoder));
|
|
43
|
+
break;
|
|
44
|
+
|
|
45
|
+
case MessageType.SyncStatus:
|
|
46
|
+
this.applySyncStatusMessage(
|
|
47
|
+
provider,
|
|
48
|
+
readVarInt(message.decoder) === 1,
|
|
49
|
+
);
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case MessageType.CLOSE:
|
|
53
|
+
// eslint-disable-next-line no-case-declarations
|
|
54
|
+
const event: CloseEvent = {
|
|
55
|
+
code: 1000,
|
|
56
|
+
reason: readVarString(message.decoder),
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
target: provider.configuration.websocketProvider.webSocket!,
|
|
59
|
+
type: "close",
|
|
60
|
+
};
|
|
61
|
+
provider.onClose();
|
|
62
|
+
provider.configuration.onClose({ event });
|
|
63
|
+
provider.forwardClose({ event });
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(`Can’t apply message of unknown type: ${type}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Reply
|
|
71
|
+
if (message.length() > emptyMessageLength + 1) {
|
|
72
|
+
// length of documentName (considered in emptyMessageLength plus length of yjs sync type, set in applySyncMessage)
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
provider.send(OutgoingMessage, { encoder: message.encoder });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private applySyncMessage(provider: HocuspocusProvider, emitSynced: boolean) {
|
|
79
|
+
const { message } = this;
|
|
80
|
+
|
|
81
|
+
message.writeVarUint(MessageType.Sync);
|
|
82
|
+
|
|
83
|
+
// Apply update
|
|
84
|
+
const syncMessageType = readSyncMessage(
|
|
85
|
+
message.decoder,
|
|
86
|
+
message.encoder,
|
|
87
|
+
provider.document,
|
|
88
|
+
provider,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Synced once we receive Step2
|
|
92
|
+
if (emitSynced && syncMessageType === messageYjsSyncStep2) {
|
|
93
|
+
provider.synced = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
applySyncStatusMessage(provider: HocuspocusProvider, applied: boolean) {
|
|
98
|
+
if (applied) {
|
|
99
|
+
provider.decrementUnsyncedChanges();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private applyAwarenessMessage(provider: HocuspocusProvider) {
|
|
104
|
+
if (!provider.awareness) return;
|
|
105
|
+
|
|
106
|
+
const { message } = this;
|
|
107
|
+
|
|
108
|
+
awarenessProtocol.applyAwarenessUpdate(
|
|
109
|
+
provider.awareness,
|
|
110
|
+
message.readVarUint8Array(),
|
|
111
|
+
provider,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private applyAuthMessage(provider: HocuspocusProvider) {
|
|
116
|
+
const { message } = this;
|
|
117
|
+
|
|
118
|
+
readAuthMessage(
|
|
119
|
+
message.decoder,
|
|
120
|
+
provider.sendToken.bind(provider),
|
|
121
|
+
provider.permissionDeniedHandler.bind(provider),
|
|
122
|
+
provider.authenticatedHandler.bind(provider),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private applyQueryAwarenessMessage(provider: HocuspocusProvider) {
|
|
127
|
+
if (!provider.awareness) return;
|
|
128
|
+
|
|
129
|
+
const { message } = this;
|
|
130
|
+
|
|
131
|
+
message.writeVarUint(MessageType.Awareness);
|
|
132
|
+
message.writeVarUint8Array(
|
|
133
|
+
awarenessProtocol.encodeAwarenessUpdate(
|
|
134
|
+
provider.awareness,
|
|
135
|
+
Array.from(provider.awareness.getStates().keys()),
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Encoder } from "lib0/encoding";
|
|
2
|
+
import { toUint8Array } from "lib0/encoding";
|
|
3
|
+
import type { ConstructableOutgoingMessage } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
export class MessageSender {
|
|
6
|
+
encoder: Encoder;
|
|
7
|
+
|
|
8
|
+
message: any;
|
|
9
|
+
|
|
10
|
+
constructor(Message: ConstructableOutgoingMessage, args: any = {}) {
|
|
11
|
+
this.message = new Message();
|
|
12
|
+
this.encoder = this.message.get(args);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
create() {
|
|
16
|
+
return toUint8Array(this.encoder);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
send(webSocket: any) {
|
|
20
|
+
webSocket?.send(this.create());
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IndexedDB-backed offline store for a single Abracadabra document.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Persist CRDT updates locally so they survive page refreshes / network loss.
|
|
6
|
+
* - Store the last-known state vector for fast reconnect diffs.
|
|
7
|
+
* - Store a permission snapshot so the UI can gate writes without a network round-trip.
|
|
8
|
+
* - Queue subdoc registration events created while offline.
|
|
9
|
+
*
|
|
10
|
+
* Falls back to a silent no-op when IndexedDB is unavailable (e.g. SSR / Node.js).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface PendingSubdoc {
|
|
14
|
+
childId: string;
|
|
15
|
+
parentId: string;
|
|
16
|
+
createdAt: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DB_VERSION = 1;
|
|
20
|
+
|
|
21
|
+
function idbAvailable(): boolean {
|
|
22
|
+
return typeof globalThis !== "undefined" && "indexedDB" in globalThis;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function openDb(docId: string): Promise<IDBDatabase> {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const req = globalThis.indexedDB.open(`abracadabra:${docId}`, DB_VERSION);
|
|
28
|
+
|
|
29
|
+
req.onupgradeneeded = (event) => {
|
|
30
|
+
const db = (event.target as IDBOpenDBRequest).result;
|
|
31
|
+
|
|
32
|
+
if (!db.objectStoreNames.contains("updates")) {
|
|
33
|
+
db.createObjectStore("updates", { autoIncrement: true });
|
|
34
|
+
}
|
|
35
|
+
if (!db.objectStoreNames.contains("meta")) {
|
|
36
|
+
db.createObjectStore("meta");
|
|
37
|
+
}
|
|
38
|
+
if (!db.objectStoreNames.contains("subdoc_queue")) {
|
|
39
|
+
db.createObjectStore("subdoc_queue", { keyPath: "childId" });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
req.onsuccess = () => resolve(req.result);
|
|
44
|
+
req.onerror = () => reject(req.error);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function txPromise<T>(
|
|
49
|
+
store: IDBObjectStore,
|
|
50
|
+
request: IDBRequest<T>,
|
|
51
|
+
): Promise<T> {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
request.onsuccess = () => resolve(request.result);
|
|
54
|
+
request.onerror = () => reject(request.error);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class OfflineStore {
|
|
59
|
+
private docId: string;
|
|
60
|
+
private db: IDBDatabase | null = null;
|
|
61
|
+
|
|
62
|
+
constructor(docId: string) {
|
|
63
|
+
this.docId = docId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private async getDb(): Promise<IDBDatabase | null> {
|
|
67
|
+
if (!idbAvailable()) return null;
|
|
68
|
+
if (!this.db) {
|
|
69
|
+
this.db = await openDb(this.docId).catch(() => null);
|
|
70
|
+
}
|
|
71
|
+
return this.db;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Updates ─────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
async persistUpdate(update: Uint8Array): Promise<void> {
|
|
77
|
+
const db = await this.getDb();
|
|
78
|
+
if (!db) return;
|
|
79
|
+
const tx = db.transaction("updates", "readwrite");
|
|
80
|
+
const store = tx.objectStore("updates");
|
|
81
|
+
await txPromise(store, store.add(update));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getPendingUpdates(): Promise<Uint8Array[]> {
|
|
85
|
+
const db = await this.getDb();
|
|
86
|
+
if (!db) return [];
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const tx = db.transaction("updates", "readonly");
|
|
89
|
+
const store = tx.objectStore("updates");
|
|
90
|
+
const req = store.getAll();
|
|
91
|
+
req.onsuccess = () => resolve(req.result as Uint8Array[]);
|
|
92
|
+
req.onerror = () => reject(req.error);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async clearPendingUpdates(): Promise<void> {
|
|
97
|
+
const db = await this.getDb();
|
|
98
|
+
if (!db) return;
|
|
99
|
+
const tx = db.transaction("updates", "readwrite");
|
|
100
|
+
await txPromise(tx.objectStore("updates"), tx.objectStore("updates").clear());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── State vector ─────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
async getStateVector(): Promise<Uint8Array | null> {
|
|
106
|
+
const db = await this.getDb();
|
|
107
|
+
if (!db) return null;
|
|
108
|
+
const tx = db.transaction("meta", "readonly");
|
|
109
|
+
const result = await txPromise<Uint8Array | undefined>(
|
|
110
|
+
tx.objectStore("meta"),
|
|
111
|
+
tx.objectStore("meta").get("sv"),
|
|
112
|
+
);
|
|
113
|
+
return result ?? null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async saveStateVector(sv: Uint8Array): Promise<void> {
|
|
117
|
+
const db = await this.getDb();
|
|
118
|
+
if (!db) return;
|
|
119
|
+
const tx = db.transaction("meta", "readwrite");
|
|
120
|
+
await txPromise(tx.objectStore("meta"), tx.objectStore("meta").put(sv, "sv"));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Permission snapshot ──────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
async getPermissionSnapshot(): Promise<string | null> {
|
|
126
|
+
const db = await this.getDb();
|
|
127
|
+
if (!db) return null;
|
|
128
|
+
const tx = db.transaction("meta", "readonly");
|
|
129
|
+
const result = await txPromise<string | undefined>(
|
|
130
|
+
tx.objectStore("meta"),
|
|
131
|
+
tx.objectStore("meta").get("role"),
|
|
132
|
+
);
|
|
133
|
+
return result ?? null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async savePermissionSnapshot(role: string): Promise<void> {
|
|
137
|
+
const db = await this.getDb();
|
|
138
|
+
if (!db) return;
|
|
139
|
+
const tx = db.transaction("meta", "readwrite");
|
|
140
|
+
await txPromise(
|
|
141
|
+
tx.objectStore("meta"),
|
|
142
|
+
tx.objectStore("meta").put(role, "role"),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── Subdoc registration queue ─────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
async queueSubdoc(entry: PendingSubdoc): Promise<void> {
|
|
149
|
+
const db = await this.getDb();
|
|
150
|
+
if (!db) return;
|
|
151
|
+
const tx = db.transaction("subdoc_queue", "readwrite");
|
|
152
|
+
await txPromise(
|
|
153
|
+
tx.objectStore("subdoc_queue"),
|
|
154
|
+
tx.objectStore("subdoc_queue").put(entry),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async getPendingSubdocs(): Promise<PendingSubdoc[]> {
|
|
159
|
+
const db = await this.getDb();
|
|
160
|
+
if (!db) return [];
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const tx = db.transaction("subdoc_queue", "readonly");
|
|
163
|
+
const req = tx.objectStore("subdoc_queue").getAll();
|
|
164
|
+
req.onsuccess = () => resolve(req.result as PendingSubdoc[]);
|
|
165
|
+
req.onerror = () => reject(req.error);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async removeSubdocFromQueue(childId: string): Promise<void> {
|
|
170
|
+
const db = await this.getDb();
|
|
171
|
+
if (!db) return;
|
|
172
|
+
const tx = db.transaction("subdoc_queue", "readwrite");
|
|
173
|
+
await txPromise(
|
|
174
|
+
tx.objectStore("subdoc_queue"),
|
|
175
|
+
tx.objectStore("subdoc_queue").delete(childId),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
destroy() {
|
|
182
|
+
this.db?.close();
|
|
183
|
+
this.db = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Encoder } from "lib0/encoding";
|
|
2
|
+
import { createEncoder, toUint8Array } from "lib0/encoding";
|
|
3
|
+
import type {
|
|
4
|
+
MessageType,
|
|
5
|
+
OutgoingMessageArguments,
|
|
6
|
+
OutgoingMessageInterface,
|
|
7
|
+
} from "./types.ts";
|
|
8
|
+
|
|
9
|
+
export class OutgoingMessage implements OutgoingMessageInterface {
|
|
10
|
+
encoder: Encoder;
|
|
11
|
+
|
|
12
|
+
type?: MessageType;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.encoder = createEncoder();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get(args: Partial<OutgoingMessageArguments>) {
|
|
19
|
+
return args.encoder;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
toUint8Array() {
|
|
23
|
+
return toUint8Array(this.encoder);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { writeVarString, writeVarUint } from "lib0/encoding";
|
|
2
|
+
import { writeAuthentication } from "@hocuspocus/common";
|
|
3
|
+
import type { OutgoingMessageArguments } from "../types.ts";
|
|
4
|
+
import { MessageType } from "../types.ts";
|
|
5
|
+
import { OutgoingMessage } from "../OutgoingMessage.ts";
|
|
6
|
+
|
|
7
|
+
export class AuthenticationMessage extends OutgoingMessage {
|
|
8
|
+
type = MessageType.Auth;
|
|
9
|
+
|
|
10
|
+
description = "Authentication";
|
|
11
|
+
|
|
12
|
+
get(args: Partial<OutgoingMessageArguments>) {
|
|
13
|
+
if (typeof args.token === "undefined") {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"The authentication message requires `token` as an argument.",
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
writeVarString(this.encoder, args.documentName!);
|
|
20
|
+
writeVarUint(this.encoder, this.type);
|
|
21
|
+
writeAuthentication(this.encoder, args.token);
|
|
22
|
+
|
|
23
|
+
return this.encoder;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as encoding from "lib0/encoding";
|
|
2
|
+
import { encodeAwarenessUpdate } from "y-protocols/awareness";
|
|
3
|
+
import type { OutgoingMessageArguments } from "../types.ts";
|
|
4
|
+
import { MessageType } from "../types.ts";
|
|
5
|
+
import { OutgoingMessage } from "../OutgoingMessage.ts";
|
|
6
|
+
|
|
7
|
+
export class AwarenessMessage extends OutgoingMessage {
|
|
8
|
+
type = MessageType.Awareness;
|
|
9
|
+
|
|
10
|
+
description = "Awareness states update";
|
|
11
|
+
|
|
12
|
+
get(args: Partial<OutgoingMessageArguments>) {
|
|
13
|
+
if (typeof args.awareness === "undefined") {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"The awareness message requires awareness as an argument",
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof args.clients === "undefined") {
|
|
20
|
+
throw new Error("The awareness message requires clients as an argument");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
encoding.writeVarString(this.encoder, args.documentName!);
|
|
24
|
+
encoding.writeVarUint(this.encoder, this.type);
|
|
25
|
+
|
|
26
|
+
let awarenessUpdate;
|
|
27
|
+
if (args.states === undefined) {
|
|
28
|
+
awarenessUpdate = encodeAwarenessUpdate(args.awareness, args.clients);
|
|
29
|
+
} else {
|
|
30
|
+
awarenessUpdate = encodeAwarenessUpdate(
|
|
31
|
+
args.awareness,
|
|
32
|
+
args.clients,
|
|
33
|
+
args.states,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
encoding.writeVarUint8Array(this.encoder, awarenessUpdate);
|
|
38
|
+
|
|
39
|
+
return this.encoder;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as encoding from "lib0/encoding";
|
|
2
|
+
import type { OutgoingMessageArguments } from "../types.ts";
|
|
3
|
+
import { MessageType } from "../types.ts";
|
|
4
|
+
import { OutgoingMessage } from "../OutgoingMessage.ts";
|
|
5
|
+
|
|
6
|
+
export class CloseMessage extends OutgoingMessage {
|
|
7
|
+
type = MessageType.CLOSE;
|
|
8
|
+
|
|
9
|
+
description = "Ask the server to close the connection";
|
|
10
|
+
|
|
11
|
+
get(args: Partial<OutgoingMessageArguments>) {
|
|
12
|
+
encoding.writeVarString(this.encoder, args.documentName!);
|
|
13
|
+
encoding.writeVarUint(this.encoder, this.type);
|
|
14
|
+
|
|
15
|
+
return this.encoder;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as encoding from "lib0/encoding";
|
|
2
|
+
import type { OutgoingMessageArguments } from "../types.ts";
|
|
3
|
+
import { MessageType } from "../types.ts";
|
|
4
|
+
import { OutgoingMessage } from "../OutgoingMessage.ts";
|
|
5
|
+
|
|
6
|
+
export class QueryAwarenessMessage extends OutgoingMessage {
|
|
7
|
+
type = MessageType.QueryAwareness;
|
|
8
|
+
|
|
9
|
+
description = "Queries awareness states";
|
|
10
|
+
|
|
11
|
+
get(args: Partial<OutgoingMessageArguments>) {
|
|
12
|
+
encoding.writeVarString(this.encoder, args.documentName!);
|
|
13
|
+
encoding.writeVarUint(this.encoder, this.type);
|
|
14
|
+
|
|
15
|
+
return this.encoder;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { writeVarString, writeVarUint } from "lib0/encoding";
|
|
2
|
+
import type { OutgoingMessageArguments } from "../types.ts";
|
|
3
|
+
import { MessageType } from "../types.ts";
|
|
4
|
+
import { OutgoingMessage } from "../OutgoingMessage.ts";
|
|
5
|
+
|
|
6
|
+
export class StatelessMessage extends OutgoingMessage {
|
|
7
|
+
type = MessageType.Stateless;
|
|
8
|
+
|
|
9
|
+
description = "A stateless message";
|
|
10
|
+
|
|
11
|
+
get(args: Partial<OutgoingMessageArguments>) {
|
|
12
|
+
writeVarString(this.encoder, args.documentName!);
|
|
13
|
+
writeVarUint(this.encoder, this.type);
|
|
14
|
+
writeVarString(this.encoder, args.payload ?? "");
|
|
15
|
+
|
|
16
|
+
return this.encoder;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { writeVarString, writeVarUint } from "lib0/encoding";
|
|
2
|
+
import type { OutgoingMessageArguments } from "../types.ts";
|
|
3
|
+
import { MessageType } from "../types.ts";
|
|
4
|
+
import { OutgoingMessage } from "../OutgoingMessage.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Registers a new subdocument with the Abracadabra server.
|
|
8
|
+
*
|
|
9
|
+
* Wire format (consumed by handle_subdoc in sync.rs):
|
|
10
|
+
* [ParentDocName: VarString] [MSG_SUBDOC(4): VarUint] [childDocumentName: VarString]
|
|
11
|
+
*
|
|
12
|
+
* The server responds with a MSG_STATELESS JSON frame:
|
|
13
|
+
* { type: "subdoc_registered", child_id, parent_id }
|
|
14
|
+
* which AbracadabraProvider intercepts in receiveStateless().
|
|
15
|
+
*/
|
|
16
|
+
export class SubdocMessage extends OutgoingMessage {
|
|
17
|
+
type = MessageType.Subdoc;
|
|
18
|
+
|
|
19
|
+
description = "SubdocRegistration";
|
|
20
|
+
|
|
21
|
+
get(args: Partial<OutgoingMessageArguments>) {
|
|
22
|
+
if (!args.documentName) {
|
|
23
|
+
throw new Error("SubdocMessage requires `documentName` (parent id).");
|
|
24
|
+
}
|
|
25
|
+
if (!args.childDocumentName) {
|
|
26
|
+
throw new Error("SubdocMessage requires `childDocumentName`.");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
writeVarString(this.encoder, args.documentName);
|
|
30
|
+
writeVarUint(this.encoder, this.type);
|
|
31
|
+
writeVarString(this.encoder, args.childDocumentName);
|
|
32
|
+
|
|
33
|
+
return this.encoder;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as encoding from "lib0/encoding";
|
|
2
|
+
import * as syncProtocol from "y-protocols/sync";
|
|
3
|
+
import type { OutgoingMessageArguments } from "../types.ts";
|
|
4
|
+
import { MessageType } from "../types.ts";
|
|
5
|
+
import { OutgoingMessage } from "../OutgoingMessage.ts";
|
|
6
|
+
|
|
7
|
+
export class SyncStepOneMessage extends OutgoingMessage {
|
|
8
|
+
type = MessageType.Sync;
|
|
9
|
+
|
|
10
|
+
description = "First sync step";
|
|
11
|
+
|
|
12
|
+
get(args: Partial<OutgoingMessageArguments>) {
|
|
13
|
+
if (typeof args.document === "undefined") {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"The sync step one message requires document as an argument",
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
encoding.writeVarString(this.encoder, args.documentName!);
|
|
20
|
+
encoding.writeVarUint(this.encoder, this.type);
|
|
21
|
+
syncProtocol.writeSyncStep1(this.encoder, args.document);
|
|
22
|
+
|
|
23
|
+
return this.encoder;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as encoding from "lib0/encoding";
|
|
2
|
+
import * as syncProtocol from "y-protocols/sync";
|
|
3
|
+
import type { OutgoingMessageArguments } from "../types.ts";
|
|
4
|
+
import { MessageType } from "../types.ts";
|
|
5
|
+
import { OutgoingMessage } from "../OutgoingMessage.ts";
|
|
6
|
+
|
|
7
|
+
export class SyncStepTwoMessage extends OutgoingMessage {
|
|
8
|
+
type = MessageType.Sync;
|
|
9
|
+
|
|
10
|
+
description = "Second sync step";
|
|
11
|
+
|
|
12
|
+
get(args: Partial<OutgoingMessageArguments>) {
|
|
13
|
+
if (typeof args.document === "undefined") {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"The sync step two message requires document as an argument",
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
encoding.writeVarString(this.encoder, args.documentName!);
|
|
20
|
+
encoding.writeVarUint(this.encoder, this.type);
|
|
21
|
+
syncProtocol.writeSyncStep2(this.encoder, args.document);
|
|
22
|
+
|
|
23
|
+
return this.encoder;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { writeVarString, writeVarUint } from "lib0/encoding";
|
|
2
|
+
import { writeUpdate } from "y-protocols/sync";
|
|
3
|
+
import type { OutgoingMessageArguments } from "../types.ts";
|
|
4
|
+
import { MessageType } from "../types.ts";
|
|
5
|
+
import { OutgoingMessage } from "../OutgoingMessage.ts";
|
|
6
|
+
|
|
7
|
+
export class UpdateMessage extends OutgoingMessage {
|
|
8
|
+
type = MessageType.Sync;
|
|
9
|
+
|
|
10
|
+
description = "A document update";
|
|
11
|
+
|
|
12
|
+
get(args: Partial<OutgoingMessageArguments>) {
|
|
13
|
+
writeVarString(this.encoder, args.documentName!);
|
|
14
|
+
writeVarUint(this.encoder, this.type);
|
|
15
|
+
|
|
16
|
+
writeUpdate(this.encoder, args.update);
|
|
17
|
+
|
|
18
|
+
return this.encoder;
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./HocuspocusProvider.ts";
|
|
2
|
+
export * from "./HocuspocusProviderWebsocket.ts";
|
|
3
|
+
export * from "./types.ts";
|
|
4
|
+
export * from "./AbracadabraProvider.ts";
|
|
5
|
+
export * from "./OfflineStore.ts";
|
|
6
|
+
export { SubdocMessage } from "./OutgoingMessages/SubdocMessage.ts";
|
|
7
|
+
export { CryptoIdentityKeystore } from "./CryptoIdentityKeystore.ts";
|