@abraca/dabra 0.6.0 → 0.8.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/abracadabra-provider.cjs +747 -66
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +725 -62
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +269 -31
- package/package.json +1 -2
- package/src/{HocuspocusProvider.ts → AbracadabraBaseProvider.ts} +33 -22
- package/src/AbracadabraClient.ts +96 -3
- package/src/AbracadabraProvider.ts +11 -11
- package/src/{HocuspocusProviderWebsocket.ts → AbracadabraWS.ts} +36 -22
- package/src/CloseEvents.ts +49 -0
- package/src/DocumentCache.ts +210 -0
- package/src/FileBlobStore.ts +300 -0
- package/src/MessageReceiver.ts +8 -8
- package/src/OutgoingMessages/AuthenticationMessage.ts +1 -1
- package/src/SearchIndex.ts +247 -0
- package/src/auth.ts +62 -0
- package/src/awarenessStatesToArray.ts +10 -0
- package/src/index.ts +9 -2
- package/src/types.ts +59 -1
package/src/AbracadabraClient.ts
CHANGED
|
@@ -6,7 +6,10 @@ import type {
|
|
|
6
6
|
PublicKeyInfo,
|
|
7
7
|
PermissionEntry,
|
|
8
8
|
HealthStatus,
|
|
9
|
+
ServerInfo,
|
|
10
|
+
InviteRow,
|
|
9
11
|
} from "./types.ts";
|
|
12
|
+
import type { DocumentCache } from "./DocumentCache.ts";
|
|
10
13
|
|
|
11
14
|
export interface AbracadabraClientConfig {
|
|
12
15
|
/** Server base URL (http or https). WebSocket URL is derived automatically. */
|
|
@@ -19,6 +22,13 @@ export interface AbracadabraClientConfig {
|
|
|
19
22
|
storageKey?: string;
|
|
20
23
|
/** Custom fetch implementation (useful for Node.js or testing). */
|
|
21
24
|
fetch?: typeof globalThis.fetch;
|
|
25
|
+
/**
|
|
26
|
+
* Optional metadata cache. When provided, read methods (getDoc, listChildren,
|
|
27
|
+
* getMe, listPermissions, listUploads) check the cache before hitting the
|
|
28
|
+
* network. Write methods (deleteDoc, upload, deleteUpload) invalidate affected
|
|
29
|
+
* cache entries automatically.
|
|
30
|
+
*/
|
|
31
|
+
cache?: DocumentCache;
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
export class AbracadabraClient {
|
|
@@ -27,12 +37,14 @@ export class AbracadabraClient {
|
|
|
27
37
|
private readonly persistAuth: boolean;
|
|
28
38
|
private readonly storageKey: string;
|
|
29
39
|
private readonly _fetch: typeof globalThis.fetch;
|
|
40
|
+
readonly cache: DocumentCache | null;
|
|
30
41
|
|
|
31
42
|
constructor(config: AbracadabraClientConfig) {
|
|
32
43
|
this.baseUrl = config.url.replace(/\/+$/, "");
|
|
33
44
|
this.persistAuth = config.persistAuth ?? typeof localStorage !== "undefined";
|
|
34
45
|
this.storageKey = config.storageKey ?? "abracadabra:auth";
|
|
35
46
|
this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
47
|
+
this.cache = config.cache ?? null;
|
|
36
48
|
|
|
37
49
|
// Load token: explicit > persisted > null
|
|
38
50
|
this._token = config.token ?? this.loadPersistedToken() ?? null;
|
|
@@ -74,6 +86,7 @@ export class AbracadabraClient {
|
|
|
74
86
|
password: string;
|
|
75
87
|
email?: string;
|
|
76
88
|
displayName?: string;
|
|
89
|
+
inviteCode?: string;
|
|
77
90
|
}): Promise<UserProfile> {
|
|
78
91
|
return this.request<UserProfile>("POST", "/auth/register", {
|
|
79
92
|
body: opts,
|
|
@@ -91,6 +104,7 @@ export class AbracadabraClient {
|
|
|
91
104
|
deviceName?: string;
|
|
92
105
|
displayName?: string;
|
|
93
106
|
email?: string;
|
|
107
|
+
inviteCode?: string;
|
|
94
108
|
}): Promise<UserProfile> {
|
|
95
109
|
const username = opts.username ?? `user-${opts.publicKey.slice(0, 8)}`;
|
|
96
110
|
return this.request<UserProfile>("POST", "/auth/register", {
|
|
@@ -100,6 +114,7 @@ export class AbracadabraClient {
|
|
|
100
114
|
deviceName: opts.deviceName,
|
|
101
115
|
displayName: opts.displayName,
|
|
102
116
|
email: opts.email,
|
|
117
|
+
inviteCode: opts.inviteCode,
|
|
103
118
|
},
|
|
104
119
|
auth: false,
|
|
105
120
|
});
|
|
@@ -175,7 +190,15 @@ export class AbracadabraClient {
|
|
|
175
190
|
|
|
176
191
|
/** Get the current user's profile. */
|
|
177
192
|
async getMe(): Promise<UserProfile> {
|
|
178
|
-
|
|
193
|
+
if (this.cache) {
|
|
194
|
+
const cached = await this.cache.getCurrentProfile();
|
|
195
|
+
if (cached) return cached;
|
|
196
|
+
}
|
|
197
|
+
const profile = await this.request<UserProfile>("GET", "/users/me");
|
|
198
|
+
if (this.cache) {
|
|
199
|
+
await this.cache.setCurrentProfile(profile).catch(() => null);
|
|
200
|
+
}
|
|
201
|
+
return profile;
|
|
179
202
|
}
|
|
180
203
|
|
|
181
204
|
/** Update the current user's display name. */
|
|
@@ -192,20 +215,38 @@ export class AbracadabraClient {
|
|
|
192
215
|
|
|
193
216
|
/** Get document metadata. */
|
|
194
217
|
async getDoc(docId: string): Promise<DocumentMeta> {
|
|
195
|
-
|
|
218
|
+
if (this.cache) {
|
|
219
|
+
const cached = await this.cache.getDoc(docId);
|
|
220
|
+
if (cached) return cached;
|
|
221
|
+
}
|
|
222
|
+
const meta = await this.request<DocumentMeta>("GET", `/docs/${encodeURIComponent(docId)}`);
|
|
223
|
+
if (this.cache) {
|
|
224
|
+
await this.cache.setDoc(meta).catch(() => null);
|
|
225
|
+
}
|
|
226
|
+
return meta;
|
|
196
227
|
}
|
|
197
228
|
|
|
198
229
|
/** Delete a document (requires Owner role). Cascades to children and uploads. */
|
|
199
230
|
async deleteDoc(docId: string): Promise<void> {
|
|
200
231
|
await this.request("DELETE", `/docs/${encodeURIComponent(docId)}`);
|
|
232
|
+
if (this.cache) {
|
|
233
|
+
await this.cache.invalidateDoc(docId).catch(() => null);
|
|
234
|
+
}
|
|
201
235
|
}
|
|
202
236
|
|
|
203
237
|
/** List immediate child documents. */
|
|
204
238
|
async listChildren(docId: string): Promise<string[]> {
|
|
239
|
+
if (this.cache) {
|
|
240
|
+
const cached = await this.cache.getChildren(docId);
|
|
241
|
+
if (cached) return cached;
|
|
242
|
+
}
|
|
205
243
|
const res = await this.request<{ children: string[] }>(
|
|
206
244
|
"GET",
|
|
207
245
|
`/docs/${encodeURIComponent(docId)}/children`,
|
|
208
246
|
);
|
|
247
|
+
if (this.cache) {
|
|
248
|
+
await this.cache.setChildren(docId, res.children).catch(() => null);
|
|
249
|
+
}
|
|
209
250
|
return res.children;
|
|
210
251
|
}
|
|
211
252
|
|
|
@@ -222,10 +263,17 @@ export class AbracadabraClient {
|
|
|
222
263
|
|
|
223
264
|
/** List all permissions for a document (requires read access). */
|
|
224
265
|
async listPermissions(docId: string): Promise<PermissionEntry[]> {
|
|
266
|
+
if (this.cache) {
|
|
267
|
+
const cached = await this.cache.getPermissions(docId);
|
|
268
|
+
if (cached) return cached;
|
|
269
|
+
}
|
|
225
270
|
const res = await this.request<{ permissions: PermissionEntry[] }>(
|
|
226
271
|
"GET",
|
|
227
272
|
`/docs/${encodeURIComponent(docId)}/permissions`,
|
|
228
273
|
);
|
|
274
|
+
if (this.cache) {
|
|
275
|
+
await this.cache.setPermissions(docId, res.permissions).catch(() => null);
|
|
276
|
+
}
|
|
229
277
|
return res.permissions;
|
|
230
278
|
}
|
|
231
279
|
|
|
@@ -273,15 +321,26 @@ export class AbracadabraClient {
|
|
|
273
321
|
if (!res.ok) {
|
|
274
322
|
throw await this.toError(res);
|
|
275
323
|
}
|
|
276
|
-
|
|
324
|
+
const meta = await res.json() as UploadMeta;
|
|
325
|
+
if (this.cache) {
|
|
326
|
+
await this.cache.invalidateUploads(docId).catch(() => null);
|
|
327
|
+
}
|
|
328
|
+
return meta;
|
|
277
329
|
}
|
|
278
330
|
|
|
279
331
|
/** List all uploads for a document. */
|
|
280
332
|
async listUploads(docId: string): Promise<UploadInfo[]> {
|
|
333
|
+
if (this.cache) {
|
|
334
|
+
const cached = await this.cache.getUploads(docId);
|
|
335
|
+
if (cached) return cached;
|
|
336
|
+
}
|
|
281
337
|
const res = await this.request<{ uploads: UploadInfo[] }>(
|
|
282
338
|
"GET",
|
|
283
339
|
`/docs/${encodeURIComponent(docId)}/uploads`,
|
|
284
340
|
);
|
|
341
|
+
if (this.cache) {
|
|
342
|
+
await this.cache.setUploads(docId, res.uploads).catch(() => null);
|
|
343
|
+
}
|
|
285
344
|
return res.uploads;
|
|
286
345
|
}
|
|
287
346
|
|
|
@@ -308,6 +367,32 @@ export class AbracadabraClient {
|
|
|
308
367
|
"DELETE",
|
|
309
368
|
`/docs/${encodeURIComponent(docId)}/uploads/${encodeURIComponent(uploadId)}`,
|
|
310
369
|
);
|
|
370
|
+
if (this.cache) {
|
|
371
|
+
await this.cache.invalidateUploads(docId).catch(() => null);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ── Invites ──────────────────────────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
/** Create an invite code (requires permission per server config). */
|
|
378
|
+
async createInvite(opts?: { role?: string; maxUses?: number; expiresIn?: number }): Promise<InviteRow> {
|
|
379
|
+
return this.request<InviteRow>("POST", "/invites", { body: opts ?? {} });
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** List invite codes visible to the current user. */
|
|
383
|
+
async listInvites(): Promise<InviteRow[]> {
|
|
384
|
+
const res = await this.request<{ invites: InviteRow[] }>("GET", "/invites");
|
|
385
|
+
return res.invites;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Revoke an invite by its code. */
|
|
389
|
+
async revokeInvite(code: string): Promise<void> {
|
|
390
|
+
await this.request("DELETE", `/invites/${encodeURIComponent(code)}`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** Redeem an invite code for the currently authenticated user. */
|
|
394
|
+
async redeemInvite(code: string): Promise<void> {
|
|
395
|
+
await this.request("POST", "/invites/redeem", { body: { code } });
|
|
311
396
|
}
|
|
312
397
|
|
|
313
398
|
// ── System ───────────────────────────────────────────────────────────────
|
|
@@ -317,6 +402,14 @@ export class AbracadabraClient {
|
|
|
317
402
|
return this.request<HealthStatus>("GET", "/health", { auth: false });
|
|
318
403
|
}
|
|
319
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Fetch server metadata including the optional `index_doc_id` entry point.
|
|
407
|
+
* No auth required.
|
|
408
|
+
*/
|
|
409
|
+
async serverInfo(): Promise<ServerInfo> {
|
|
410
|
+
return this.request<ServerInfo>("GET", "/info", { auth: false });
|
|
411
|
+
}
|
|
412
|
+
|
|
320
413
|
// ── Internals ────────────────────────────────────────────────────────────
|
|
321
414
|
|
|
322
415
|
private async request<T = void>(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Y from "yjs";
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
2
|
+
import { AbracadabraBaseProvider } from "./AbracadabraBaseProvider.ts";
|
|
3
|
+
import type { AbracadabraBaseProviderConfiguration } from "./AbracadabraBaseProvider.ts";
|
|
4
|
+
import type { AbracadabraWS } from "./AbracadabraWS.ts";
|
|
5
5
|
import { OfflineStore } from "./OfflineStore.ts";
|
|
6
6
|
import { SubdocMessage } from "./OutgoingMessages/SubdocMessage.ts";
|
|
7
7
|
import { UpdateMessage } from "./OutgoingMessages/UpdateMessage.ts";
|
|
@@ -15,7 +15,7 @@ import { AuthenticationMessage } from "./OutgoingMessages/AuthenticationMessage.
|
|
|
15
15
|
import type { AbracadabraClient } from "./AbracadabraClient.ts";
|
|
16
16
|
|
|
17
17
|
export interface AbracadabraProviderConfiguration
|
|
18
|
-
extends Omit<
|
|
18
|
+
extends Omit<AbracadabraBaseProviderConfiguration, "url" | "websocketProvider"> {
|
|
19
19
|
/**
|
|
20
20
|
* Subdocument loading strategy.
|
|
21
21
|
* - "lazy" (default) – child providers are created only when explicitly requested.
|
|
@@ -58,7 +58,7 @@ export interface AbracadabraProviderConfiguration
|
|
|
58
58
|
url?: string;
|
|
59
59
|
|
|
60
60
|
/** Shared WebSocket connection (use when multiplexing multiple root documents). */
|
|
61
|
-
websocketProvider?:
|
|
61
|
+
websocketProvider?: AbracadabraWS;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/** Validate that a string is a UUID acceptable by the server's DocId parser. */
|
|
@@ -67,7 +67,7 @@ function isValidDocId(id: string): boolean {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
* AbracadabraProvider extends
|
|
70
|
+
* AbracadabraProvider extends AbracadabraBaseProvider with:
|
|
71
71
|
*
|
|
72
72
|
* 1. Subdocument lifecycle – intercepts Y.Doc subdoc events and syncs them
|
|
73
73
|
* with the server via MSG_SUBDOC (4) frames. Child documents get their
|
|
@@ -87,7 +87,7 @@ function isValidDocId(id: string): boolean {
|
|
|
87
87
|
* can gate write operations without a network round-trip. Role is
|
|
88
88
|
* refreshed from the server on every reconnect.
|
|
89
89
|
*/
|
|
90
|
-
export class AbracadabraProvider extends
|
|
90
|
+
export class AbracadabraProvider extends AbracadabraBaseProvider {
|
|
91
91
|
public effectiveRole: EffectiveRole = null;
|
|
92
92
|
|
|
93
93
|
private _client: AbracadabraClient | null;
|
|
@@ -110,7 +110,7 @@ export class AbracadabraProvider extends HocuspocusProvider {
|
|
|
110
110
|
|
|
111
111
|
constructor(configuration: AbracadabraProviderConfiguration) {
|
|
112
112
|
// Derive URL and token from client when not explicitly set.
|
|
113
|
-
const resolved = { ...configuration } as
|
|
113
|
+
const resolved = { ...configuration } as AbracadabraBaseProviderConfiguration;
|
|
114
114
|
const client = configuration.client ?? null;
|
|
115
115
|
|
|
116
116
|
if (client) {
|
|
@@ -165,7 +165,7 @@ export class AbracadabraProvider extends HocuspocusProvider {
|
|
|
165
165
|
try {
|
|
166
166
|
const url =
|
|
167
167
|
config.url ??
|
|
168
|
-
(config.websocketProvider as
|
|
168
|
+
(config.websocketProvider as AbracadabraWS | undefined)?.url ??
|
|
169
169
|
client?.wsUrl;
|
|
170
170
|
if (url) return new URL(url).hostname;
|
|
171
171
|
} catch {
|
|
@@ -429,7 +429,7 @@ export class AbracadabraProvider extends HocuspocusProvider {
|
|
|
429
429
|
this.registerSubdoc(childDoc);
|
|
430
430
|
|
|
431
431
|
// Each child gets its own WebSocket connection. Omitting
|
|
432
|
-
// websocketProvider lets
|
|
432
|
+
// websocketProvider lets AbracadabraBaseProvider create one automatically
|
|
433
433
|
// (manageSocket = true), so we do NOT call attach() manually.
|
|
434
434
|
const childProvider = new AbracadabraProvider({
|
|
435
435
|
name: childId,
|
|
@@ -527,7 +527,7 @@ export class AbracadabraProvider extends HocuspocusProvider {
|
|
|
527
527
|
|
|
528
528
|
get isConnected(): boolean {
|
|
529
529
|
return (
|
|
530
|
-
(this.configuration.websocketProvider as
|
|
530
|
+
(this.configuration.websocketProvider as AbracadabraWS)
|
|
531
531
|
.status === "connected"
|
|
532
532
|
);
|
|
533
533
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { WsReadyStates } from "
|
|
1
|
+
import { WsReadyStates } from "./types.ts";
|
|
2
2
|
import { retry } from "@lifeomic/attempt";
|
|
3
3
|
import * as time from "lib0/time";
|
|
4
4
|
import type { Event, MessageEvent } from "ws";
|
|
5
5
|
import EventEmitter from "./EventEmitter.ts";
|
|
6
|
-
import type {
|
|
6
|
+
import type { AbracadabraBaseProvider } from "./AbracadabraBaseProvider.ts";
|
|
7
7
|
import { IncomingMessage } from "./IncomingMessage.ts";
|
|
8
8
|
import { CloseMessage } from "./OutgoingMessages/CloseMessage.ts";
|
|
9
9
|
import {
|
|
@@ -18,15 +18,21 @@ import {
|
|
|
18
18
|
type onStatusParameters,
|
|
19
19
|
} from "./types.ts";
|
|
20
20
|
|
|
21
|
-
export type
|
|
22
|
-
|
|
21
|
+
export type AbracadabraWebSocketConn = WebSocket & { identifier: string };
|
|
22
|
+
/** @deprecated Use AbracadabraWebSocketConn */
|
|
23
|
+
export type HocuspocusWebSocket = AbracadabraWebSocketConn;
|
|
24
|
+
/** @deprecated Use AbracadabraWebSocketConn */
|
|
25
|
+
export type HocusPocusWebSocket = AbracadabraWebSocketConn;
|
|
23
26
|
|
|
24
|
-
export type
|
|
25
|
-
Pick<
|
|
27
|
+
export type AbracadabraWSConfiguration = Required<
|
|
28
|
+
Pick<CompleteAbracadabraWSConfiguration, "url">
|
|
26
29
|
> &
|
|
27
|
-
Partial<
|
|
30
|
+
Partial<CompleteAbracadabraWSConfiguration>;
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
/** @deprecated Use AbracadabraWSConfiguration */
|
|
33
|
+
export type HocuspocusProviderWebsocketConfiguration = AbracadabraWSConfiguration;
|
|
34
|
+
|
|
35
|
+
export interface CompleteAbracadabraWSConfiguration {
|
|
30
36
|
/**
|
|
31
37
|
* Whether to connect automatically when creating the provider instance. Default=true
|
|
32
38
|
*/
|
|
@@ -99,13 +105,16 @@ export interface CompleteHocuspocusProviderWebsocketConfiguration {
|
|
|
99
105
|
/**
|
|
100
106
|
* Map of attached providers keyed by documentName.
|
|
101
107
|
*/
|
|
102
|
-
providerMap: Map<string,
|
|
108
|
+
providerMap: Map<string, AbracadabraBaseProvider>;
|
|
103
109
|
}
|
|
104
110
|
|
|
105
|
-
|
|
111
|
+
/** @deprecated Use CompleteAbracadabraWSConfiguration */
|
|
112
|
+
export type CompleteHocuspocusProviderWebsocketConfiguration = CompleteAbracadabraWSConfiguration;
|
|
113
|
+
|
|
114
|
+
export class AbracadabraWS extends EventEmitter {
|
|
106
115
|
private messageQueue: any[] = [];
|
|
107
116
|
|
|
108
|
-
public configuration:
|
|
117
|
+
public configuration: CompleteAbracadabraWSConfiguration = {
|
|
109
118
|
url: "",
|
|
110
119
|
autoConnect: true,
|
|
111
120
|
preserveTrailingSlash: false,
|
|
@@ -144,7 +153,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
144
153
|
providerMap: new Map(),
|
|
145
154
|
};
|
|
146
155
|
|
|
147
|
-
webSocket:
|
|
156
|
+
webSocket: AbracadabraWebSocketConn | null = null;
|
|
148
157
|
|
|
149
158
|
webSocketHandlers: { [key: string]: any } = {};
|
|
150
159
|
|
|
@@ -165,7 +174,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
165
174
|
reject: (reason?: any) => void;
|
|
166
175
|
} | null = null;
|
|
167
176
|
|
|
168
|
-
constructor(configuration:
|
|
177
|
+
constructor(configuration: AbracadabraWSConfiguration) {
|
|
169
178
|
super();
|
|
170
179
|
this.setConfiguration(configuration);
|
|
171
180
|
|
|
@@ -208,7 +217,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
208
217
|
this.receivedOnOpenPayload = event;
|
|
209
218
|
}
|
|
210
219
|
|
|
211
|
-
attach(provider:
|
|
220
|
+
attach(provider: AbracadabraBaseProvider) {
|
|
212
221
|
this.configuration.providerMap.set(provider.configuration.name, provider);
|
|
213
222
|
|
|
214
223
|
if (this.status === WebSocketStatus.Disconnected && this.shouldConnect) {
|
|
@@ -220,7 +229,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
220
229
|
}
|
|
221
230
|
}
|
|
222
231
|
|
|
223
|
-
detach(provider:
|
|
232
|
+
detach(provider: AbracadabraBaseProvider) {
|
|
224
233
|
if (this.configuration.providerMap.has(provider.configuration.name)) {
|
|
225
234
|
provider.send(CloseMessage, {
|
|
226
235
|
documentName: provider.configuration.name,
|
|
@@ -230,7 +239,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
230
239
|
}
|
|
231
240
|
|
|
232
241
|
public setConfiguration(
|
|
233
|
-
configuration: Partial<
|
|
242
|
+
configuration: Partial<AbracadabraWSConfiguration> = {},
|
|
234
243
|
): void {
|
|
235
244
|
this.configuration = { ...this.configuration, ...configuration };
|
|
236
245
|
|
|
@@ -274,7 +283,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
274
283
|
}
|
|
275
284
|
},
|
|
276
285
|
}).catch((error: any) => {
|
|
277
|
-
// If we aborted the connection attempt then don
|
|
286
|
+
// If we aborted the connection attempt then don't throw an error
|
|
278
287
|
// ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
|
|
279
288
|
if (error && error.code !== "ATTEMPT_ABORTED") {
|
|
280
289
|
throw error;
|
|
@@ -296,7 +305,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
296
305
|
}
|
|
297
306
|
|
|
298
307
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
299
|
-
attachWebSocketListeners(ws:
|
|
308
|
+
attachWebSocketListeners(ws: AbracadabraWebSocketConn, reject: Function) {
|
|
300
309
|
const { identifier } = ws;
|
|
301
310
|
const onMessageHandler = (payload: any) => this.emit("message", payload);
|
|
302
311
|
const onCloseHandler = (payload: any) => this.emit("close", { event: payload });
|
|
@@ -410,17 +419,17 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
410
419
|
closeTries = 0;
|
|
411
420
|
|
|
412
421
|
checkConnection() {
|
|
413
|
-
// Don
|
|
422
|
+
// Don't check the connection when it's not even established
|
|
414
423
|
if (this.status !== WebSocketStatus.Connected) {
|
|
415
424
|
return;
|
|
416
425
|
}
|
|
417
426
|
|
|
418
|
-
// Don
|
|
427
|
+
// Don't close the connection while waiting for the first message
|
|
419
428
|
if (!this.lastMessageReceived) {
|
|
420
429
|
return;
|
|
421
430
|
}
|
|
422
431
|
|
|
423
|
-
// Don
|
|
432
|
+
// Don't close the connection when a message was received recently
|
|
424
433
|
if (
|
|
425
434
|
this.configuration.messageReconnectTimeout >=
|
|
426
435
|
time.getUnixTime() - this.lastMessageReceived
|
|
@@ -497,7 +506,7 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
497
506
|
this.rejectConnectionAttempt();
|
|
498
507
|
}
|
|
499
508
|
|
|
500
|
-
// Let
|
|
509
|
+
// Let's update the connection status.
|
|
501
510
|
this.status = WebSocketStatus.Disconnected;
|
|
502
511
|
this.emit("status", { status: WebSocketStatus.Disconnected });
|
|
503
512
|
|
|
@@ -535,3 +544,8 @@ export class HocuspocusProviderWebsocket extends EventEmitter {
|
|
|
535
544
|
this.cleanupWebSocket();
|
|
536
545
|
}
|
|
537
546
|
}
|
|
547
|
+
|
|
548
|
+
/** @deprecated Use AbracadabraWS */
|
|
549
|
+
export const HocuspocusProviderWebsocket = AbracadabraWS;
|
|
550
|
+
/** @deprecated Use AbracadabraWS */
|
|
551
|
+
export type HocuspocusProviderWebsocket = AbracadabraWS;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface CloseEvent {
|
|
2
|
+
code: number;
|
|
3
|
+
reason: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The server is terminating the connection because a data frame was received
|
|
8
|
+
* that is too large.
|
|
9
|
+
* See: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
|
|
10
|
+
*/
|
|
11
|
+
export const MessageTooBig: CloseEvent = {
|
|
12
|
+
code: 1009,
|
|
13
|
+
reason: "Message Too Big",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The server successfully processed the request, asks that the requester reset
|
|
18
|
+
* its document view, and is not returning any content.
|
|
19
|
+
*/
|
|
20
|
+
export const ResetConnection: CloseEvent = {
|
|
21
|
+
code: 4205,
|
|
22
|
+
reason: "Reset Connection",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Similar to Forbidden, but specifically for use when authentication is required and has
|
|
27
|
+
* failed or has not yet been provided.
|
|
28
|
+
*/
|
|
29
|
+
export const Unauthorized: CloseEvent = {
|
|
30
|
+
code: 4401,
|
|
31
|
+
reason: "Unauthorized",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The request contained valid data and was understood by the server, but the server
|
|
36
|
+
* is refusing action.
|
|
37
|
+
*/
|
|
38
|
+
export const Forbidden: CloseEvent = {
|
|
39
|
+
code: 4403,
|
|
40
|
+
reason: "Forbidden",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The server timed out waiting for the request.
|
|
45
|
+
*/
|
|
46
|
+
export const ConnectionTimeout: CloseEvent = {
|
|
47
|
+
code: 4408,
|
|
48
|
+
reason: "Connection Timeout",
|
|
49
|
+
};
|