@palantir/pack.state.foundry-event 0.0.1-beta.1
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/.turbo/turbo-lint.log +57 -0
- package/.turbo/turbo-transpileBrowser.log +5 -0
- package/.turbo/turbo-transpileCjs.log +5 -0
- package/.turbo/turbo-transpileEsm.log +5 -0
- package/.turbo/turbo-transpileTypes.log +5 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/LICENSE.txt +13 -0
- package/build/browser/index.js +403 -0
- package/build/browser/index.js.map +1 -0
- package/build/cjs/index.cjs +427 -0
- package/build/cjs/index.cjs.map +1 -0
- package/build/cjs/index.d.cts +78 -0
- package/build/esm/index.js +403 -0
- package/build/esm/index.js.map +1 -0
- package/build/types/FoundryEventService.d.ts +21 -0
- package/build/types/FoundryEventService.d.ts.map +1 -0
- package/build/types/__tests__/EventServiceCometD.reconnection.test.d.ts +1 -0
- package/build/types/__tests__/EventServiceCometD.reconnection.test.d.ts.map +1 -0
- package/build/types/cometd/EventServiceCometD.d.ts +20 -0
- package/build/types/cometd/EventServiceCometD.d.ts.map +1 -0
- package/build/types/index.d.ts +4 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/types/EventService.d.ts +37 -0
- package/build/types/types/EventService.d.ts.map +1 -0
- package/package.json +71 -0
- package/src/FoundryEventService.ts +285 -0
- package/src/__tests__/EventServiceCometD.reconnection.test.ts +498 -0
- package/src/cometd/EventServiceCometD.ts +291 -0
- package/src/index.ts +27 -0
- package/src/types/EventService.ts +69 -0
- package/tsconfig.json +21 -0
- package/vitest.config.mjs +26 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
|
|
2
|
+
> @palantir/pack.state.foundry-event@0.0.1-beta.1 lint /home/runner/work/pack/pack/packages/state/foundry-event
|
|
3
|
+
> eslint ./src ; dprint check --config $(find-up dprint.json) --allow-no-files
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/home/runner/work/pack/pack/packages/state/foundry-event/src/__tests__/EventServiceCometD.reconnection.test.ts
|
|
7
|
+
152:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
8
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
9
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
10
|
+
208:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
11
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
12
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
13
|
+
209:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
14
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
15
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
16
|
+
245:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
17
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
18
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
19
|
+
246:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
20
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
21
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
22
|
+
250:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
23
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
24
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
25
|
+
254:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
26
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
27
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
28
|
+
292:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
29
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
30
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
31
|
+
324:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
32
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
33
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
34
|
+
325:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
35
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
36
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
37
|
+
350:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
38
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
39
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
40
|
+
351:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
41
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
42
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
43
|
+
400:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
44
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
45
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
46
|
+
431:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
47
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
48
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
49
|
+
432:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
50
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
51
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
52
|
+
433:14 warning A method that is not declared with `this: void` may cause unintentional scoping of `this` when separated from its object.
|
|
53
|
+
Consider using an arrow function or explicitly `.bind()`ing the method to avoid calling the method with an unintended `this` value.
|
|
54
|
+
If a function does not access `this`, it can be annotated with `this: void` @typescript-eslint/unbound-method
|
|
55
|
+
|
|
56
|
+
✖ 16 problems (0 errors, 16 warnings)
|
|
57
|
+
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2025 Palantir Technologies, Inc.
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { getAuthModule } from '@palantir/pack.auth';
|
|
2
|
+
import { CometD, AckExtension } from 'cometd';
|
|
3
|
+
import 'cometd/AckExtension.js';
|
|
4
|
+
import { justOnce, generateId } from '@palantir/pack.core';
|
|
5
|
+
import { DocumentLoadStatus } from '@palantir/pack.state.core';
|
|
6
|
+
import { Base64 } from 'js-base64';
|
|
7
|
+
import * as y from 'yjs';
|
|
8
|
+
|
|
9
|
+
// src/cometd/EventServiceCometD.ts
|
|
10
|
+
var BEARER_TOKEN_FIELD = "bearer-token";
|
|
11
|
+
var EXTENSION_ACK = "AckExtension";
|
|
12
|
+
var EXTENSION_HANDSHAKE_TOKEN = "handshakeToken";
|
|
13
|
+
var META_CHANNEL_HANDSHAKE = "/meta/handshake";
|
|
14
|
+
var EventServiceCometD = class {
|
|
15
|
+
logger;
|
|
16
|
+
initializePromise;
|
|
17
|
+
subscriptionById = /* @__PURE__ */ new Map();
|
|
18
|
+
tokenExtension = new TokenExtension();
|
|
19
|
+
nextSubscriptionHandle = 0;
|
|
20
|
+
constructor(app, cometd = new CometD()) {
|
|
21
|
+
this.app = app;
|
|
22
|
+
this.cometd = cometd;
|
|
23
|
+
this.logger = app.config.logger.child({}, {
|
|
24
|
+
msgPrefix: "EventServiceCometD"
|
|
25
|
+
});
|
|
26
|
+
this.configureCometd();
|
|
27
|
+
this.cometd.registerExtension(EXTENSION_ACK, new AckExtension());
|
|
28
|
+
this.cometd.registerExtension(EXTENSION_HANDSHAKE_TOKEN, this.tokenExtension);
|
|
29
|
+
getAuthModule(app).onTokenChange((token) => {
|
|
30
|
+
this.tokenExtension.setToken(token);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
initialize() {
|
|
34
|
+
if (this.initializePromise != null) {
|
|
35
|
+
return this.initializePromise;
|
|
36
|
+
}
|
|
37
|
+
this.initializePromise = new Promise((resolve) => {
|
|
38
|
+
this.cometd.addListener(META_CHANNEL_HANDSHAKE, ({
|
|
39
|
+
clientId,
|
|
40
|
+
connectionType,
|
|
41
|
+
error,
|
|
42
|
+
successful
|
|
43
|
+
}) => {
|
|
44
|
+
if (successful) {
|
|
45
|
+
this.logger.info("CometD handshake successful", {
|
|
46
|
+
clientId,
|
|
47
|
+
connectionType
|
|
48
|
+
});
|
|
49
|
+
resolve();
|
|
50
|
+
this.cometd.batch(() => {
|
|
51
|
+
this.subscriptionById.forEach((subscription, subscriptionId) => {
|
|
52
|
+
this.logger.debug("Resubscribing to channel", {
|
|
53
|
+
channel: subscription.eventChannel,
|
|
54
|
+
subscriptionId
|
|
55
|
+
});
|
|
56
|
+
const subscribeProps = subscription.getSubscriptionRequest?.();
|
|
57
|
+
subscription.handle = this.cometd.resubscribe(subscription.handle, {
|
|
58
|
+
ext: subscribeProps
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
this.logger.warn("CometD handshake failed", {
|
|
64
|
+
clientId,
|
|
65
|
+
error
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const authModule = getAuthModule(this.app);
|
|
70
|
+
void authModule.getToken().then((token) => {
|
|
71
|
+
this.logger.info("Initializing CometD with token");
|
|
72
|
+
this.tokenExtension.setToken(token);
|
|
73
|
+
this.cometd.handshake();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
return this.initializePromise;
|
|
77
|
+
}
|
|
78
|
+
async subscribe(channel, onMessage, getSubscriptionRequest) {
|
|
79
|
+
await this.initialize();
|
|
80
|
+
const subscriptionId = (this.nextSubscriptionHandle++).toString(10);
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
const messageHandler = (receivedData) => {
|
|
83
|
+
if (!this.subscriptionById.has(subscriptionId)) {
|
|
84
|
+
this.logger.info("Dropping message for unsubscribing channel", {
|
|
85
|
+
channel,
|
|
86
|
+
subscriptionId
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.logger.debug("Received message on channel", {
|
|
91
|
+
channel,
|
|
92
|
+
subscriptionId,
|
|
93
|
+
hasData: receivedData.data != null
|
|
94
|
+
});
|
|
95
|
+
onMessage(receivedData.data);
|
|
96
|
+
};
|
|
97
|
+
const subscribeCallback = (message) => {
|
|
98
|
+
if (message.successful) {
|
|
99
|
+
this.logger.debug("Successfully subscribed to channel", {
|
|
100
|
+
channel,
|
|
101
|
+
subscriptionId
|
|
102
|
+
});
|
|
103
|
+
resolve(subscriptionId);
|
|
104
|
+
} else {
|
|
105
|
+
this.subscriptionById.delete(subscriptionId);
|
|
106
|
+
const maybeFailureReason = "failure" in message && typeof message.failure === "object" && message.failure && "reason" in message.failure && typeof message.failure.reason === "string" ? message.failure.reason : void 0;
|
|
107
|
+
this.logger.error("Failed to subscribe to channel ", {
|
|
108
|
+
channel,
|
|
109
|
+
error: message.error,
|
|
110
|
+
maybeFailureReason
|
|
111
|
+
});
|
|
112
|
+
const errorMessage = message.error ?? (maybeFailureReason != null ? `(no error message provided by server, a guess: ${maybeFailureReason})` : "(no error message provided by server)");
|
|
113
|
+
reject(new Error(`Failed to subscribe to channel ${channel}: ${errorMessage}`));
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const ext = getSubscriptionRequest?.();
|
|
117
|
+
const subscriptionHandle = ext != null ? this.cometd.subscribe(channel, messageHandler, {
|
|
118
|
+
ext
|
|
119
|
+
}, subscribeCallback) : this.cometd.subscribe(channel, messageHandler, subscribeCallback);
|
|
120
|
+
this.subscriptionById.set(subscriptionId, {
|
|
121
|
+
handle: subscriptionHandle,
|
|
122
|
+
getSubscriptionRequest,
|
|
123
|
+
eventChannel: channel
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
unsubscribe(subscriptionId) {
|
|
128
|
+
const subscription = this.subscriptionById.get(subscriptionId);
|
|
129
|
+
if (subscription == null) {
|
|
130
|
+
this.logger.warn("Attempted to unsubscribe from unknown subscriptionId", {
|
|
131
|
+
subscriptionId
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const {
|
|
136
|
+
handle,
|
|
137
|
+
eventChannel
|
|
138
|
+
} = subscription;
|
|
139
|
+
this.subscriptionById.delete(subscriptionId);
|
|
140
|
+
this.cometd.unsubscribe(handle, (message) => {
|
|
141
|
+
if (!message.successful) {
|
|
142
|
+
this.logger.warn("Server unsubscribe confirmation failed", {
|
|
143
|
+
eventChannel,
|
|
144
|
+
subscriptionId,
|
|
145
|
+
error: message.error
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async publish(channel, content) {
|
|
151
|
+
await this.initialize();
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
this.cometd.publish(channel, content, (message) => {
|
|
154
|
+
if (message.successful) {
|
|
155
|
+
this.logger.debug("Successfully published message", channel, {
|
|
156
|
+
content
|
|
157
|
+
});
|
|
158
|
+
resolve();
|
|
159
|
+
} else {
|
|
160
|
+
const error = new Error(`Failed to publish to ${channel}`, {
|
|
161
|
+
cause: message.error
|
|
162
|
+
});
|
|
163
|
+
this.logger.error("Failed to publish", {
|
|
164
|
+
channel,
|
|
165
|
+
error
|
|
166
|
+
});
|
|
167
|
+
reject(error);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
setLogLevel(logLevel) {
|
|
173
|
+
this.configureCometd(logLevel);
|
|
174
|
+
}
|
|
175
|
+
configureCometd(logLevel) {
|
|
176
|
+
const url = getCometDWebsocketUrl(this.app.config);
|
|
177
|
+
this.logger.info("Configuring cometD ", {
|
|
178
|
+
url
|
|
179
|
+
});
|
|
180
|
+
this.cometd.configure({
|
|
181
|
+
url,
|
|
182
|
+
// NOTE : Allow higher latency on busy networks to avoid retry loops when servers or networks fall behind.
|
|
183
|
+
maxNetworkDelay: 3e4,
|
|
184
|
+
logLevel,
|
|
185
|
+
autoBatch: true
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
function getCometDWebsocketUrl(appConfig) {
|
|
190
|
+
const httpUrl = new URL(`${appConfig.remote.packWsPath}/cometd`, appConfig.remote.baseUrl);
|
|
191
|
+
httpUrl.protocol.replace(/https?/, "ws");
|
|
192
|
+
return httpUrl.href;
|
|
193
|
+
}
|
|
194
|
+
var TokenExtension = class {
|
|
195
|
+
currentToken;
|
|
196
|
+
setToken(token) {
|
|
197
|
+
this.currentToken = token;
|
|
198
|
+
}
|
|
199
|
+
outgoing = (message) => {
|
|
200
|
+
if (message.channel === META_CHANNEL_HANDSHAKE) {
|
|
201
|
+
if (this.currentToken) {
|
|
202
|
+
message.ext ??= {};
|
|
203
|
+
message.ext[BEARER_TOKEN_FIELD] = this.currentToken;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return message;
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
var UPDATE_ORIGIN_REMOTE = "remote";
|
|
210
|
+
var getDocumentUpdatesChannelId = (documentId) => `/document/${documentId}/updates`;
|
|
211
|
+
var getDocumentPublishChannelId = (documentId) => `/document/${documentId}/publish`;
|
|
212
|
+
var FoundryEventService = class {
|
|
213
|
+
eventService;
|
|
214
|
+
logger;
|
|
215
|
+
sessions = /* @__PURE__ */ new Map();
|
|
216
|
+
constructor(app, cometd) {
|
|
217
|
+
this.app = app;
|
|
218
|
+
this.eventService = new EventServiceCometD(app, cometd);
|
|
219
|
+
this.logger = app.config.logger.child({}, {
|
|
220
|
+
level: "debug",
|
|
221
|
+
msgPrefix: "FoundryEventService"
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
startDocumentSync(documentId, yDoc, onStatusChange) {
|
|
225
|
+
const sessionId = this.getSessionId(documentId);
|
|
226
|
+
if (this.sessions.has(sessionId)) {
|
|
227
|
+
throw new Error(`Sync session already exists for document ${documentId}`);
|
|
228
|
+
}
|
|
229
|
+
const session = {
|
|
230
|
+
clientId: crypto.randomUUID(),
|
|
231
|
+
documentId,
|
|
232
|
+
documentSubscriptionId: void 0,
|
|
233
|
+
lastRevisionId: void 0,
|
|
234
|
+
localYDocUpdateHandler: void 0,
|
|
235
|
+
yDoc
|
|
236
|
+
};
|
|
237
|
+
this.sessions.set(sessionId, session);
|
|
238
|
+
const localYDocUpdateHandler = (update, origin) => {
|
|
239
|
+
if (origin === UPDATE_ORIGIN_REMOTE) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const lastRevisionId = session.lastRevisionId;
|
|
243
|
+
if (lastRevisionId == null) {
|
|
244
|
+
this.logger.error("Cannot publish document update before initial load is complete. The local state will remain inconsistent.", {
|
|
245
|
+
docId: documentId
|
|
246
|
+
});
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const publishChannelId = getDocumentPublishChannelId(documentId);
|
|
250
|
+
const editId = generateId();
|
|
251
|
+
void this.eventService.publish(publishChannelId, {
|
|
252
|
+
clientId: session.clientId,
|
|
253
|
+
editId,
|
|
254
|
+
yjsUpdate: {
|
|
255
|
+
data: Base64.fromUint8Array(update)
|
|
256
|
+
}
|
|
257
|
+
}).catch((error) => {
|
|
258
|
+
this.logger.error("Failed to publish document update", error, {
|
|
259
|
+
docId: documentId
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
session.localYDocUpdateHandler = localYDocUpdateHandler;
|
|
264
|
+
yDoc.on("update", localYDocUpdateHandler);
|
|
265
|
+
onStatusChange({
|
|
266
|
+
load: DocumentLoadStatus.LOADING
|
|
267
|
+
});
|
|
268
|
+
const channelId = getDocumentUpdatesChannelId(documentId);
|
|
269
|
+
this.eventService.subscribe(channelId, (message) => {
|
|
270
|
+
if (session.localYDocUpdateHandler !== localYDocUpdateHandler) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
this.handleDocumentUpdateMessage(session, message, onStatusChange);
|
|
274
|
+
}, () => ({
|
|
275
|
+
clientId: session.clientId,
|
|
276
|
+
lastRevisionId: session.lastRevisionId?.toString()
|
|
277
|
+
})).then((subscriptionId) => {
|
|
278
|
+
if (session.localYDocUpdateHandler === localYDocUpdateHandler) {
|
|
279
|
+
session.documentSubscriptionId = subscriptionId;
|
|
280
|
+
} else {
|
|
281
|
+
this.eventService.unsubscribe(subscriptionId);
|
|
282
|
+
this.sessions.delete(this.getSessionId(documentId));
|
|
283
|
+
}
|
|
284
|
+
}).catch((e) => {
|
|
285
|
+
if (session.localYDocUpdateHandler === localYDocUpdateHandler) {
|
|
286
|
+
const error = new Error("Failed to setup document data subscription", {
|
|
287
|
+
cause: e
|
|
288
|
+
});
|
|
289
|
+
onStatusChange({
|
|
290
|
+
error,
|
|
291
|
+
load: DocumentLoadStatus.ERROR
|
|
292
|
+
});
|
|
293
|
+
} else {
|
|
294
|
+
this.logger.warn("Document data subscription error after subscription was closed", {
|
|
295
|
+
docId: documentId,
|
|
296
|
+
error: e
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
return {
|
|
301
|
+
clientId: session.clientId,
|
|
302
|
+
documentId: session.documentId
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
stopDocumentSync(session) {
|
|
306
|
+
const sessionId = this.getSessionId(session.documentId);
|
|
307
|
+
const internalSession = this.sessions.get(sessionId);
|
|
308
|
+
if (internalSession == null) {
|
|
309
|
+
this.logger.warn("Attempted to stop sync for unknown session", {
|
|
310
|
+
documentId: session.documentId,
|
|
311
|
+
clientId: session.clientId
|
|
312
|
+
});
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (internalSession.localYDocUpdateHandler != null) {
|
|
316
|
+
internalSession.yDoc.off("update", internalSession.localYDocUpdateHandler);
|
|
317
|
+
internalSession.localYDocUpdateHandler = void 0;
|
|
318
|
+
}
|
|
319
|
+
if (internalSession.documentSubscriptionId) {
|
|
320
|
+
this.eventService.unsubscribe(internalSession.documentSubscriptionId);
|
|
321
|
+
this.sessions.delete(sessionId);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
handleDocumentUpdateMessage(session, message, onStatusChange) {
|
|
325
|
+
switch (message.type) {
|
|
326
|
+
case "error":
|
|
327
|
+
const {
|
|
328
|
+
args,
|
|
329
|
+
code,
|
|
330
|
+
errorInstanceId
|
|
331
|
+
} = message;
|
|
332
|
+
this.logger.error("Received document update error message", {
|
|
333
|
+
docId: session.documentId,
|
|
334
|
+
code,
|
|
335
|
+
errorInstanceId,
|
|
336
|
+
args
|
|
337
|
+
});
|
|
338
|
+
onStatusChange({
|
|
339
|
+
error: new Error(`Subscription in error state [${errorInstanceId}]`, {
|
|
340
|
+
cause: message
|
|
341
|
+
}),
|
|
342
|
+
load: DocumentLoadStatus.ERROR
|
|
343
|
+
});
|
|
344
|
+
break;
|
|
345
|
+
case "update":
|
|
346
|
+
const {
|
|
347
|
+
baseRevisionId,
|
|
348
|
+
clientId,
|
|
349
|
+
revisionId,
|
|
350
|
+
update
|
|
351
|
+
} = message;
|
|
352
|
+
const data = update != null && typeof update.data === "string" ? Base64.toUint8Array(update.data) : void 0;
|
|
353
|
+
const messageDetail = {
|
|
354
|
+
baseRevisionId,
|
|
355
|
+
clientId,
|
|
356
|
+
revisionId,
|
|
357
|
+
updateSize: data?.byteLength ?? 0
|
|
358
|
+
};
|
|
359
|
+
if (session.lastRevisionId != null && Number(baseRevisionId) !== session.lastRevisionId) {
|
|
360
|
+
this.logger.error("Got unexpected update for baseRevisionId", {
|
|
361
|
+
docId: session.documentId,
|
|
362
|
+
lastRevisionId: session.lastRevisionId,
|
|
363
|
+
message: messageDetail
|
|
364
|
+
});
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
this.logger.debug("Applying remote Y.js update", {
|
|
368
|
+
docId: session.documentId,
|
|
369
|
+
lastRevisionId: session.lastRevisionId,
|
|
370
|
+
message: messageDetail
|
|
371
|
+
});
|
|
372
|
+
session.lastRevisionId = Number(revisionId);
|
|
373
|
+
if (data != null) {
|
|
374
|
+
y.applyUpdate(session.yDoc, data, UPDATE_ORIGIN_REMOTE);
|
|
375
|
+
}
|
|
376
|
+
onStatusChange({
|
|
377
|
+
load: DocumentLoadStatus.LOADED
|
|
378
|
+
});
|
|
379
|
+
break;
|
|
380
|
+
default:
|
|
381
|
+
const {
|
|
382
|
+
type
|
|
383
|
+
} = message;
|
|
384
|
+
justOnce(`unknown-collab-update-type:${type}`, () => {
|
|
385
|
+
this.logger.warn("Received unknown DocumentUpdateMessage type. This is only warned the first occurrence.", {
|
|
386
|
+
docId: session.documentId,
|
|
387
|
+
updateType: type
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
getSessionId(documentId) {
|
|
394
|
+
return documentId;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
function createFoundryEventService(app, cometd) {
|
|
398
|
+
return new FoundryEventService(app, cometd);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export { EventServiceCometD, FoundryEventService, createFoundryEventService };
|
|
402
|
+
//# sourceMappingURL=index.js.map
|
|
403
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cometd/EventServiceCometD.ts","../../src/FoundryEventService.ts"],"names":[],"mappings":";;;;;;;;;AAoBA,IAAM,kBAAA,GAAqB,cAAA;AAC3B,IAAM,aAAA,GAAgB,cAAA;AACtB,IAAM,yBAAA,GAA4B,gBAAA;AAClC,IAAM,sBAAA,GAAyB,iBAAA;AACxB,IAAM,qBAAN,MAAyB;AAAA,EAC9B,MAAA;AAAA,EACA,iBAAA;AAAA,EACA,gBAAA,uBAAuB,GAAA,EAAI;AAAA,EAC3B,cAAA,GAAiB,IAAI,cAAA,EAAe;AAAA,EACpC,sBAAA,GAAyB,CAAA;AAAA,EACzB,WAAA,CAAY,GAAA,EAAK,MAAA,GAAS,IAAI,QAAO,EAAG;AACtC,IAAA,IAAA,CAAK,GAAA,GAAM,GAAA;AACX,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,SAAS,GAAA,CAAI,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,EAAC,EAAG;AAAA,MACxC,SAAA,EAAW;AAAA,KACZ,CAAA;AACD,IAAA,IAAA,CAAK,eAAA,EAAgB;AACrB,IAAA,IAAA,CAAK,MAAA,CAAO,iBAAA,CAAkB,aAAA,EAAe,IAAI,cAAc,CAAA;AAC/D,IAAA,IAAA,CAAK,MAAA,CAAO,iBAAA,CAAkB,yBAAA,EAA2B,IAAA,CAAK,cAAc,CAAA;AAO5E,IAAA,aAAA,CAAc,GAAG,CAAA,CAAE,aAAA,CAAc,CAAA,KAAA,KAAS;AACxC,MAAA,IAAA,CAAK,cAAA,CAAe,SAAS,KAAK,CAAA;AAAA,IACpC,CAAC,CAAA;AAAA,EACH;AAAA,EACA,UAAA,GAAa;AACX,IAAA,IAAI,IAAA,CAAK,qBAAqB,IAAA,EAAM;AAClC,MAAA,OAAO,IAAA,CAAK,iBAAA;AAAA,IACd;AACA,IAAA,IAAA,CAAK,iBAAA,GAAoB,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW;AAC9C,MAAA,IAAA,CAAK,MAAA,CAAO,WAAA,CAAY,sBAAA,EAAwB,CAAC;AAAA,QAC/C,QAAA;AAAA,QACA,cAAA;AAAA,QACA,KAAA;AAAA,QACA;AAAA,OACF,KAAM;AACJ,QAAA,IAAI,UAAA,EAAY;AACd,UAAA,IAAA,CAAK,MAAA,CAAO,KAAK,6BAAA,EAA+B;AAAA,YAC9C,QAAA;AAAA,YACA;AAAA,WACD,CAAA;AACD,UAAA,OAAA,EAAQ;AACR,UAAA,IAAA,CAAK,MAAA,CAAO,MAAM,MAAM;AACtB,YAAA,IAAA,CAAK,gBAAA,CAAiB,OAAA,CAAQ,CAAC,YAAA,EAAc,cAAA,KAAmB;AAC9D,cAAA,IAAA,CAAK,MAAA,CAAO,MAAM,0BAAA,EAA4B;AAAA,gBAC5C,SAAS,YAAA,CAAa,YAAA;AAAA,gBACtB;AAAA,eACD,CAAA;AACD,cAAA,MAAM,cAAA,GAAiB,aAAa,sBAAA,IAAyB;AAC7D,cAAA,YAAA,CAAa,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,WAAA,CAAY,aAAa,MAAA,EAAQ;AAAA,gBACjE,GAAA,EAAK;AAAA,eACN,CAAA;AAAA,YACH,CAAC,CAAA;AAAA,UACH,CAAC,CAAA;AAAA,QACH,CAAA,MAAO;AACL,UAAA,IAAA,CAAK,MAAA,CAAO,KAAK,yBAAA,EAA2B;AAAA,YAC1C,QAAA;AAAA,YACA;AAAA,WACD,CAAA;AAAA,QACH;AAAA,MACF,CAAC,CAAA;AACD,MAAA,MAAM,UAAA,GAAa,aAAA,CAAc,IAAA,CAAK,GAAG,CAAA;AACzC,MAAA,KAAK,UAAA,CAAW,QAAA,EAAS,CAAE,IAAA,CAAK,CAAA,KAAA,KAAS;AACvC,QAAA,IAAA,CAAK,MAAA,CAAO,KAAK,gCAAgC,CAAA;AACjD,QAAA,IAAA,CAAK,cAAA,CAAe,SAAS,KAAK,CAAA;AAClC,QAAA,IAAA,CAAK,OAAO,SAAA,EAAU;AAAA,MACxB,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AACD,IAAA,OAAO,IAAA,CAAK,iBAAA;AAAA,EACd;AAAA,EACA,MAAM,SAAA,CAAU,OAAA,EAAS,SAAA,EAAW,sBAAA,EAAwB;AAC1D,IAAA,MAAM,KAAK,UAAA,EAAW;AACtB,IAAA,MAAM,cAAA,GAAA,CAAkB,IAAA,CAAK,sBAAA,EAAA,EAA0B,QAAA,CAAS,EAAE,CAAA;AAClE,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,MAAA,MAAM,iBAAiB,CAAA,YAAA,KAAgB;AACrC,QAAA,IAAI,CAAC,IAAA,CAAK,gBAAA,CAAiB,GAAA,CAAI,cAAc,CAAA,EAAG;AAC9C,UAAA,IAAA,CAAK,MAAA,CAAO,KAAK,4CAAA,EAA8C;AAAA,YAC7D,OAAA;AAAA,YACA;AAAA,WACD,CAAA;AACD,UAAA;AAAA,QACF;AACA,QAAA,IAAA,CAAK,MAAA,CAAO,MAAM,6BAAA,EAA+B;AAAA,UAC/C,OAAA;AAAA,UACA,cAAA;AAAA,UACA,OAAA,EAAS,aAAa,IAAA,IAAQ;AAAA,SAC/B,CAAA;AACD,QAAA,SAAA,CAAU,aAAa,IAAI,CAAA;AAAA,MAC7B,CAAA;AACA,MAAA,MAAM,oBAAoB,CAAA,OAAA,KAAW;AACnC,QAAA,IAAI,QAAQ,UAAA,EAAY;AACtB,UAAA,IAAA,CAAK,MAAA,CAAO,MAAM,oCAAA,EAAsC;AAAA,YACtD,OAAA;AAAA,YACA;AAAA,WACD,CAAA;AACD,UAAA,OAAA,CAAQ,cAAc,CAAA;AAAA,QACxB,CAAA,MAAO;AACL,UAAA,IAAA,CAAK,gBAAA,CAAiB,OAAO,cAAc,CAAA;AAE3C,UAAA,MAAM,qBAAqB,SAAA,IAAa,OAAA,IAAW,OAAO,OAAA,CAAQ,OAAA,KAAY,YAAY,OAAA,CAAQ,OAAA,IAAW,YAAY,OAAA,CAAQ,OAAA,IAAW,OAAO,OAAA,CAAQ,OAAA,CAAQ,WAAW,QAAA,GAAW,OAAA,CAAQ,QAAQ,MAAA,GAAS,MAAA;AAClN,UAAA,IAAA,CAAK,MAAA,CAAO,MAAM,iCAAA,EAAmC;AAAA,YACnD,OAAA;AAAA,YACA,OAAO,OAAA,CAAQ,KAAA;AAAA,YACf;AAAA,WACD,CAAA;AACD,UAAA,MAAM,eAAe,OAAA,CAAQ,KAAA,KAAU,sBAAsB,IAAA,GAAO,CAAA,+CAAA,EAAkD,kBAAkB,CAAA,CAAA,CAAA,GAAM,uCAAA,CAAA;AAC9I,UAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,+BAAA,EAAkC,OAAO,CAAA,EAAA,EAAK,YAAY,EAAE,CAAC,CAAA;AAAA,QAChF;AAAA,MACF,CAAA;AACA,MAAA,MAAM,MAAM,sBAAA,IAAyB;AACrC,MAAA,MAAM,qBAAqB,GAAA,IAAO,IAAA,GAAO,KAAK,MAAA,CAAO,SAAA,CAAU,SAAS,cAAA,EAAgB;AAAA,QACtF;AAAA,OACF,EAAG,iBAAiB,CAAA,GAAI,IAAA,CAAK,OAAO,SAAA,CAAU,OAAA,EAAS,gBAAgB,iBAAiB,CAAA;AACxF,MAAA,IAAA,CAAK,gBAAA,CAAiB,IAAI,cAAA,EAAgB;AAAA,QACxC,MAAA,EAAQ,kBAAA;AAAA,QACR,sBAAA;AAAA,QACA,YAAA,EAAc;AAAA,OACf,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAAA,EACA,YAAY,cAAA,EAAgB;AAC1B,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,gBAAA,CAAiB,GAAA,CAAI,cAAc,CAAA;AAC7D,IAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,MAAA,IAAA,CAAK,MAAA,CAAO,KAAK,sDAAA,EAAwD;AAAA,QACvE;AAAA,OACD,CAAA;AACD,MAAA;AAAA,IACF;AACA,IAAA,MAAM;AAAA,MACJ,MAAA;AAAA,MACA;AAAA,KACF,GAAI,YAAA;AACJ,IAAA,IAAA,CAAK,gBAAA,CAAiB,OAAO,cAAc,CAAA;AAC3C,IAAA,IAAA,CAAK,MAAA,CAAO,WAAA,CAAY,MAAA,EAAQ,CAAA,OAAA,KAAW;AACzC,MAAA,IAAI,CAAC,QAAQ,UAAA,EAAY;AACvB,QAAA,IAAA,CAAK,MAAA,CAAO,KAAK,wCAAA,EAA0C;AAAA,UACzD,YAAA;AAAA,UACA,cAAA;AAAA,UACA,OAAO,OAAA,CAAQ;AAAA,SAChB,CAAA;AAAA,MACH;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AAAA,EACA,MAAM,OAAA,CAAQ,OAAA,EAAS,OAAA,EAAS;AAC9B,IAAA,MAAM,KAAK,UAAA,EAAW;AACtB,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,MAAA,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,OAAA,EAAS,OAAA,EAAS,CAAA,OAAA,KAAW;AAC/C,QAAA,IAAI,QAAQ,UAAA,EAAY;AACtB,UAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,gCAAA,EAAkC,OAAA,EAAS;AAAA,YAC3D;AAAA,WACD,CAAA;AACD,UAAA,OAAA,EAAQ;AAAA,QACV,CAAA,MAAO;AACL,UAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,CAAM,CAAA,qBAAA,EAAwB,OAAO,CAAA,CAAA,EAAI;AAAA,YACzD,OAAO,OAAA,CAAQ;AAAA,WAChB,CAAA;AACD,UAAA,IAAA,CAAK,MAAA,CAAO,MAAM,mBAAA,EAAqB;AAAA,YACrC,OAAA;AAAA,YACA;AAAA,WACD,CAAA;AACD,UAAA,MAAA,CAAO,KAAK,CAAA;AAAA,QACd;AAAA,MACF,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAAA,EACA,YAAY,QAAA,EAAU;AACpB,IAAA,IAAA,CAAK,gBAAgB,QAAQ,CAAA;AAAA,EAC/B;AAAA,EACA,gBAAgB,QAAA,EAAU;AACxB,IAAA,MAAM,GAAA,GAAM,qBAAA,CAAsB,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA;AACjD,IAAA,IAAA,CAAK,MAAA,CAAO,KAAK,qBAAA,EAAuB;AAAA,MACtC;AAAA,KACD,CAAA;AACD,IAAA,IAAA,CAAK,OAAO,SAAA,CAAU;AAAA,MACpB,GAAA;AAAA;AAAA,MAEA,eAAA,EAAiB,GAAA;AAAA,MACjB,QAAA;AAAA,MACA,SAAA,EAAW;AAAA,KACZ,CAAA;AAAA,EACH;AACF;AACA,SAAS,sBAAsB,SAAA,EAAW;AACxC,EAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAI,CAAA,EAAG,SAAA,CAAU,OAAO,UAAU,CAAA,OAAA,CAAA,EAAW,SAAA,CAAU,MAAA,CAAO,OAAO,CAAA;AACzF,EAAA,OAAA,CAAQ,QAAA,CAAS,OAAA,CAAQ,QAAA,EAAU,IAAI,CAAA;AAEvC,EAAA,OAAO,OAAA,CAAQ,IAAA;AACjB;AACA,IAAM,iBAAN,MAAqB;AAAA,EACnB,YAAA;AAAA,EACA,SAAS,KAAA,EAAO;AACd,IAAA,IAAA,CAAK,YAAA,GAAe,KAAA;AAAA,EACtB;AAAA,EACA,WAAW,CAAA,OAAA,KAAW;AAEpB,IAAA,IAAI,OAAA,CAAQ,YAAY,sBAAA,EAAwB;AAC9C,MAAA,IAAI,KAAK,YAAA,EAAc;AACrB,QAAA,OAAA,CAAQ,QAAQ,EAAC;AACjB,QAAA,OAAA,CAAQ,GAAA,CAAI,kBAAkB,CAAA,GAAI,IAAA,CAAK,YAAA;AAAA,MACzC;AAAA,IACF;AACA,IAAA,OAAO,OAAA;AAAA,EACT,CAAA;AACF,CAAA;AC9MA,IAAM,oBAAA,GAAuB,QAAA;AAC7B,IAAM,2BAAA,GAA8B,CAAA,UAAA,KAAc,CAAA,UAAA,EAAa,UAAU,CAAA,QAAA,CAAA;AACzE,IAAM,2BAAA,GAA8B,CAAA,UAAA,KAAc,CAAA,UAAA,EAAa,UAAU,CAAA,QAAA,CAAA;AAClE,IAAM,sBAAN,MAA0B;AAAA,EAC/B,YAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA,uBAAe,GAAA,EAAI;AAAA,EACnB,WAAA,CAAY,KAAK,MAAA,EAAQ;AACvB,IAAA,IAAA,CAAK,GAAA,GAAM,GAAA;AACX,IAAA,IAAA,CAAK,YAAA,GAAe,IAAI,kBAAA,CAAmB,GAAA,EAAK,MAAM,CAAA;AACtD,IAAA,IAAA,CAAK,SAAS,GAAA,CAAI,MAAA,CAAO,MAAA,CAAO,KAAA,CAAM,EAAC,EAAG;AAAA,MACxC,KAAA,EAAO,OAAA;AAAA,MACP,SAAA,EAAW;AAAA,KACZ,CAAA;AAAA,EACH;AAAA,EACA,iBAAA,CAAkB,UAAA,EAAY,IAAA,EAAM,cAAA,EAAgB;AAClD,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,YAAA,CAAa,UAAU,CAAA;AAC9C,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,SAAS,CAAA,EAAG;AAChC,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,yCAAA,EAA4C,UAAU,CAAA,CAAE,CAAA;AAAA,IAC1E;AACA,IAAA,MAAM,OAAA,GAAU;AAAA,MACd,QAAA,EAAU,OAAO,UAAA,EAAW;AAAA,MAC5B,UAAA;AAAA,MACA,sBAAA,EAAwB,MAAA;AAAA,MACxB,cAAA,EAAgB,MAAA;AAAA,MAChB,sBAAA,EAAwB,MAAA;AAAA,MACxB;AAAA,KACF;AACA,IAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,SAAA,EAAW,OAAO,CAAA;AACpC,IAAA,MAAM,sBAAA,GAAyB,CAAC,MAAA,EAAQ,MAAA,KAAW;AACjD,MAAA,IAAI,WAAW,oBAAA,EAAsB;AACnC,QAAA;AAAA,MACF;AACA,MAAA,MAAM,iBAAiB,OAAA,CAAQ,cAAA;AAC/B,MAAA,IAAI,kBAAkB,IAAA,EAAM;AAC1B,QAAA,IAAA,CAAK,MAAA,CAAO,MAAM,2GAAA,EAA6G;AAAA,UAC7H,KAAA,EAAO;AAAA,SACR,CAAA;AACD,QAAA;AAAA,MACF;AACA,MAAA,MAAM,gBAAA,GAAmB,4BAA4B,UAAU,CAAA;AAC/D,MAAA,MAAM,SAAS,UAAA,EAAW;AAC1B,MAAA,KAAK,IAAA,CAAK,YAAA,CAAa,OAAA,CAAQ,gBAAA,EAAkB;AAAA,QAC/C,UAAU,OAAA,CAAQ,QAAA;AAAA,QAClB,MAAA;AAAA,QACA,SAAA,EAAW;AAAA,UACT,IAAA,EAAM,MAAA,CAAO,cAAA,CAAe,MAAM;AAAA;AACpC,OACD,CAAA,CAAE,KAAA,CAAM,CAAA,KAAA,KAAS;AAChB,QAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,mCAAA,EAAqC,KAAA,EAAO;AAAA,UAC5D,KAAA,EAAO;AAAA,SACR,CAAA;AAAA,MACH,CAAC,CAAA;AAAA,IACH,CAAA;AACA,IAAA,OAAA,CAAQ,sBAAA,GAAyB,sBAAA;AACjC,IAAA,IAAA,CAAK,EAAA,CAAG,UAAU,sBAAsB,CAAA;AACxC,IAAA,cAAA,CAAe;AAAA,MACb,MAAM,kBAAA,CAAmB;AAAA,KAC1B,CAAA;AACD,IAAA,MAAM,SAAA,GAAY,4BAA4B,UAAU,CAAA;AACxD,IAAA,IAAA,CAAK,YAAA,CAAa,SAAA,CAAU,SAAA,EAAW,CAAA,OAAA,KAAW;AAChD,MAAA,IAAI,OAAA,CAAQ,2BAA2B,sBAAA,EAAwB;AAC7D,QAAA;AAAA,MACF;AACA,MAAA,IAAA,CAAK,2BAAA,CAA4B,OAAA,EAAS,OAAA,EAAS,cAAc,CAAA;AAAA,IACnE,GAAG,OAAO;AAAA,MACR,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,cAAA,EAAgB,OAAA,CAAQ,cAAA,EAAgB,QAAA;AAAS,KACnD,CAAE,CAAA,CAAE,IAAA,CAAK,CAAA,cAAA,KAAkB;AACzB,MAAA,IAAI,OAAA,CAAQ,2BAA2B,sBAAA,EAAwB;AAC7D,QAAA,OAAA,CAAQ,sBAAA,GAAyB,cAAA;AAAA,MACnC,CAAA,MAAO;AACL,QAAA,IAAA,CAAK,YAAA,CAAa,YAAY,cAAc,CAAA;AAC5C,QAAA,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,YAAA,CAAa,UAAU,CAAC,CAAA;AAAA,MACpD;AAAA,IACF,CAAC,CAAA,CAAE,KAAA,CAAM,CAAA,CAAA,KAAK;AACZ,MAAA,IAAI,OAAA,CAAQ,2BAA2B,sBAAA,EAAwB;AAC7D,QAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,CAAM,4CAAA,EAA8C;AAAA,UACpE,KAAA,EAAO;AAAA,SACR,CAAA;AACD,QAAA,cAAA,CAAe;AAAA,UACb,KAAA;AAAA,UACA,MAAM,kBAAA,CAAmB;AAAA,SAC1B,CAAA;AAAA,MACH,CAAA,MAAO;AACL,QAAA,IAAA,CAAK,MAAA,CAAO,KAAK,gEAAA,EAAkE;AAAA,UACjF,KAAA,EAAO,UAAA;AAAA,UACP,KAAA,EAAO;AAAA,SACR,CAAA;AAAA,MACH;AAAA,IACF,CAAC,CAAA;AACD,IAAA,OAAO;AAAA,MACL,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,YAAY,OAAA,CAAQ;AAAA,KACtB;AAAA,EACF;AAAA,EACA,iBAAiB,OAAA,EAAS;AACxB,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,YAAA,CAAa,OAAA,CAAQ,UAAU,CAAA;AACtD,IAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,SAAS,CAAA;AACnD,IAAA,IAAI,mBAAmB,IAAA,EAAM;AAC3B,MAAA,IAAA,CAAK,MAAA,CAAO,KAAK,4CAAA,EAA8C;AAAA,QAC7D,YAAY,OAAA,CAAQ,UAAA;AAAA,QACpB,UAAU,OAAA,CAAQ;AAAA,OACnB,CAAA;AACD,MAAA;AAAA,IACF;AACA,IAAA,IAAI,eAAA,CAAgB,0BAA0B,IAAA,EAAM;AAClD,MAAA,eAAA,CAAgB,IAAA,CAAK,GAAA,CAAI,QAAA,EAAU,eAAA,CAAgB,sBAAsB,CAAA;AACzE,MAAA,eAAA,CAAgB,sBAAA,GAAyB,MAAA;AAAA,IAC3C;AACA,IAAA,IAAI,gBAAgB,sBAAA,EAAwB;AAC1C,MAAA,IAAA,CAAK,YAAA,CAAa,WAAA,CAAY,eAAA,CAAgB,sBAAsB,CAAA;AACpE,MAAA,IAAA,CAAK,QAAA,CAAS,OAAO,SAAS,CAAA;AAAA,IAChC;AAAA,EACF;AAAA,EACA,2BAAA,CAA4B,OAAA,EAAS,OAAA,EAAS,cAAA,EAAgB;AAC5D,IAAA,QAAQ,QAAQ,IAAA;AAAM,MACpB,KAAK,OAAA;AACH,QAAA,MAAM;AAAA,UACJ,IAAA;AAAA,UACA,IAAA;AAAA,UACA;AAAA,SACF,GAAI,OAAA;AACJ,QAAA,IAAA,CAAK,MAAA,CAAO,MAAM,wCAAA,EAA0C;AAAA,UAC1D,OAAO,OAAA,CAAQ,UAAA;AAAA,UACf,IAAA;AAAA,UACA,eAAA;AAAA,UACA;AAAA,SACD,CAAA;AACD,QAAA,cAAA,CAAe;AAAA,UACb,KAAA,EAAO,IAAI,KAAA,CAAM,CAAA,6BAAA,EAAgC,eAAe,CAAA,CAAA,CAAA,EAAK;AAAA,YACnE,KAAA,EAAO;AAAA,WACR,CAAA;AAAA,UACD,MAAM,kBAAA,CAAmB;AAAA,SAC1B,CAAA;AACD,QAAA;AAAA,MACF,KAAK,QAAA;AACH,QAAA,MAAM;AAAA,UACJ,cAAA;AAAA,UACA,QAAA;AAAA,UACA,UAAA;AAAA,UACA;AAAA,SACF,GAAI,OAAA;AACJ,QAAA,MAAM,IAAA,GAAO,MAAA,IAAU,IAAA,IAAQ,OAAO,MAAA,CAAO,IAAA,KAAS,QAAA,GAAW,MAAA,CAAO,YAAA,CAAa,MAAA,CAAO,IAAI,CAAA,GAAI,MAAA;AACpG,QAAA,MAAM,aAAA,GAAgB;AAAA,UACpB,cAAA;AAAA,UACA,QAAA;AAAA,UACA,UAAA;AAAA,UACA,UAAA,EAAY,MAAM,UAAA,IAAc;AAAA,SAClC;AAGA,QAAA,IAAI,QAAQ,cAAA,IAAkB,IAAA,IAAQ,OAAO,cAAc,CAAA,KAAM,QAAQ,cAAA,EAAgB;AACvF,UAAA,IAAA,CAAK,MAAA,CAAO,MAAM,0CAAA,EAA4C;AAAA,YAC5D,OAAO,OAAA,CAAQ,UAAA;AAAA,YACf,gBAAgB,OAAA,CAAQ,cAAA;AAAA,YACxB,OAAA,EAAS;AAAA,WACV,CAAA;AACD,UAAA;AAAA,QACF;AACA,QAAA,IAAA,CAAK,MAAA,CAAO,MAAM,6BAAA,EAA+B;AAAA,UAC/C,OAAO,OAAA,CAAQ,UAAA;AAAA,UACf,gBAAgB,OAAA,CAAQ,cAAA;AAAA,UACxB,OAAA,EAAS;AAAA,SACV,CAAA;AACD,QAAA,OAAA,CAAQ,cAAA,GAAiB,OAAO,UAAU,CAAA;AAC1C,QAAA,IAAI,QAAQ,IAAA,EAAM;AAChB,UAAE,CAAA,CAAA,WAAA,CAAY,OAAA,CAAQ,IAAA,EAAM,IAAA,EAAM,oBAAoB,CAAA;AAAA,QACxD;AACA,QAAA,cAAA,CAAe;AAAA,UACb,MAAM,kBAAA,CAAmB;AAAA,SAC1B,CAAA;AACD,QAAA;AAAA,MACF;AAEE,QAAA,MAAM;AAAA,UACJ;AAAA,SACF,GAAI,OAAA;AACJ,QAAA,QAAA,CAAS,CAAA,2BAAA,EAA8B,IAAI,CAAA,CAAA,EAAI,MAAM;AACnD,UAAA,IAAA,CAAK,MAAA,CAAO,KAAK,wFAAA,EAA0F;AAAA,YACzG,OAAO,OAAA,CAAQ,UAAA;AAAA,YACf,UAAA,EAAY;AAAA,WACb,CAAA;AAAA,QACH,CAAC,CAAA;AACD,QAAA;AAAA;AACJ,EACF;AAAA,EACA,aAAa,UAAA,EAAY;AACvB,IAAA,OAAO,UAAA;AAAA,EACT;AACF;AACO,SAAS,yBAAA,CAA0B,KAAK,MAAA,EAAQ;AACrD,EAAA,OAAO,IAAI,mBAAA,CAAoB,GAAA,EAAK,MAAM,CAAA;AAC5C","file":"index.js","sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getAuthModule } from \"@palantir/pack.auth\";\nimport { AckExtension, CometD } from \"cometd\";\n// side-effect shenanigans imports the class impl to cometd module\nimport \"cometd/AckExtension.js\";\nconst BEARER_TOKEN_FIELD = \"bearer-token\";\nconst EXTENSION_ACK = \"AckExtension\";\nconst EXTENSION_HANDSHAKE_TOKEN = \"handshakeToken\";\nconst META_CHANNEL_HANDSHAKE = \"/meta/handshake\";\nexport class EventServiceCometD {\n logger;\n initializePromise;\n subscriptionById = new Map();\n tokenExtension = new TokenExtension();\n nextSubscriptionHandle = 0;\n constructor(app, cometd = new CometD()) {\n this.app = app;\n this.cometd = cometd;\n this.logger = app.config.logger.child({}, {\n msgPrefix: \"EventServiceCometD\"\n });\n this.configureCometd();\n this.cometd.registerExtension(EXTENSION_ACK, new AckExtension());\n this.cometd.registerExtension(EXTENSION_HANDSHAKE_TOKEN, this.tokenExtension);\n\n // TODO: Support binary messages\n // this.cometd.registerExtension(BINARY_EXTENSION_NAME, new BinaryExtension());\n\n // Any time the token changes, update the extension so reconnection requests use the new token.\n // This will also be called on initial token set when the auth module is initialized.\n getAuthModule(app).onTokenChange(token => {\n this.tokenExtension.setToken(token);\n });\n }\n initialize() {\n if (this.initializePromise != null) {\n return this.initializePromise;\n }\n this.initializePromise = new Promise(resolve => {\n this.cometd.addListener(META_CHANNEL_HANDSHAKE, ({\n clientId,\n connectionType,\n error,\n successful\n }) => {\n if (successful) {\n this.logger.info(\"CometD handshake successful\", {\n clientId,\n connectionType\n });\n resolve();\n this.cometd.batch(() => {\n this.subscriptionById.forEach((subscription, subscriptionId) => {\n this.logger.debug(\"Resubscribing to channel\", {\n channel: subscription.eventChannel,\n subscriptionId\n });\n const subscribeProps = subscription.getSubscriptionRequest?.();\n subscription.handle = this.cometd.resubscribe(subscription.handle, {\n ext: subscribeProps\n });\n });\n });\n } else {\n this.logger.warn(\"CometD handshake failed\", {\n clientId,\n error\n });\n }\n });\n const authModule = getAuthModule(this.app);\n void authModule.getToken().then(token => {\n this.logger.info(\"Initializing CometD with token\");\n this.tokenExtension.setToken(token);\n this.cometd.handshake();\n });\n });\n return this.initializePromise;\n }\n async subscribe(channel, onMessage, getSubscriptionRequest) {\n await this.initialize();\n const subscriptionId = (this.nextSubscriptionHandle++).toString(10);\n return new Promise((resolve, reject) => {\n const messageHandler = receivedData => {\n if (!this.subscriptionById.has(subscriptionId)) {\n this.logger.info(\"Dropping message for unsubscribing channel\", {\n channel,\n subscriptionId\n });\n return;\n }\n this.logger.debug(\"Received message on channel\", {\n channel,\n subscriptionId,\n hasData: receivedData.data != null\n });\n onMessage(receivedData.data);\n };\n const subscribeCallback = message => {\n if (message.successful) {\n this.logger.debug(\"Successfully subscribed to channel\", {\n channel,\n subscriptionId\n });\n resolve(subscriptionId);\n } else {\n this.subscriptionById.delete(subscriptionId);\n // TODO: Is failure really expected? It's not on the type...\n const maybeFailureReason = \"failure\" in message && typeof message.failure === \"object\" && message.failure && \"reason\" in message.failure && typeof message.failure.reason === \"string\" ? message.failure.reason : undefined;\n this.logger.error(\"Failed to subscribe to channel \", {\n channel,\n error: message.error,\n maybeFailureReason\n });\n const errorMessage = message.error ?? (maybeFailureReason != null ? `(no error message provided by server, a guess: ${maybeFailureReason})` : \"(no error message provided by server)\");\n reject(new Error(`Failed to subscribe to channel ${channel}: ${errorMessage}`));\n }\n };\n const ext = getSubscriptionRequest?.();\n const subscriptionHandle = ext != null ? this.cometd.subscribe(channel, messageHandler, {\n ext\n }, subscribeCallback) : this.cometd.subscribe(channel, messageHandler, subscribeCallback);\n this.subscriptionById.set(subscriptionId, {\n handle: subscriptionHandle,\n getSubscriptionRequest,\n eventChannel: channel\n });\n });\n }\n unsubscribe(subscriptionId) {\n const subscription = this.subscriptionById.get(subscriptionId);\n if (subscription == null) {\n this.logger.warn(\"Attempted to unsubscribe from unknown subscriptionId\", {\n subscriptionId\n });\n return;\n }\n const {\n handle,\n eventChannel\n } = subscription;\n this.subscriptionById.delete(subscriptionId);\n this.cometd.unsubscribe(handle, message => {\n if (!message.successful) {\n this.logger.warn(\"Server unsubscribe confirmation failed\", {\n eventChannel,\n subscriptionId,\n error: message.error\n });\n }\n });\n }\n async publish(channel, content) {\n await this.initialize();\n return new Promise((resolve, reject) => {\n this.cometd.publish(channel, content, message => {\n if (message.successful) {\n this.logger.debug(\"Successfully published message\", channel, {\n content\n });\n resolve();\n } else {\n const error = new Error(`Failed to publish to ${channel}`, {\n cause: message.error\n });\n this.logger.error(\"Failed to publish\", {\n channel,\n error\n });\n reject(error);\n }\n });\n });\n }\n setLogLevel(logLevel) {\n this.configureCometd(logLevel);\n }\n configureCometd(logLevel) {\n const url = getCometDWebsocketUrl(this.app.config);\n this.logger.info(\"Configuring cometD \", {\n url\n });\n this.cometd.configure({\n url,\n // NOTE : Allow higher latency on busy networks to avoid retry loops when servers or networks fall behind.\n maxNetworkDelay: 30_000,\n logLevel,\n autoBatch: true\n });\n }\n}\nfunction getCometDWebsocketUrl(appConfig) {\n const httpUrl = new URL(`${appConfig.remote.packWsPath}/cometd`, appConfig.remote.baseUrl);\n httpUrl.protocol.replace(/https?/, \"ws\");\n // TODO: likely need a specific port for ingest - all of this should be in in appConfig\n return httpUrl.href;\n}\nclass TokenExtension {\n currentToken;\n setToken(token) {\n this.currentToken = token;\n }\n outgoing = message => {\n // Always use the latest token when doing a handshake (handles reconnects)\n if (message.channel === META_CHANNEL_HANDSHAKE) {\n if (this.currentToken) {\n message.ext ??= {};\n message.ext[BEARER_TOKEN_FIELD] = this.currentToken;\n }\n }\n return message;\n };\n}","/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { generateId, justOnce } from \"@palantir/pack.core\";\nimport { DocumentLoadStatus } from \"@palantir/pack.state.core\";\nimport { Base64 } from \"js-base64\";\nimport * as y from \"yjs\";\nimport { EventServiceCometD } from \"./cometd/EventServiceCometD.js\";\nconst UPDATE_ORIGIN_REMOTE = \"remote\";\nconst getDocumentUpdatesChannelId = documentId => `/document/${documentId}/updates`;\nconst getDocumentPublishChannelId = documentId => `/document/${documentId}/publish`;\nexport class FoundryEventService {\n eventService;\n logger;\n sessions = new Map();\n constructor(app, cometd) {\n this.app = app;\n this.eventService = new EventServiceCometD(app, cometd);\n this.logger = app.config.logger.child({}, {\n level: \"debug\",\n msgPrefix: \"FoundryEventService\"\n });\n }\n startDocumentSync(documentId, yDoc, onStatusChange) {\n const sessionId = this.getSessionId(documentId);\n if (this.sessions.has(sessionId)) {\n throw new Error(`Sync session already exists for document ${documentId}`);\n }\n const session = {\n clientId: crypto.randomUUID(),\n documentId,\n documentSubscriptionId: undefined,\n lastRevisionId: undefined,\n localYDocUpdateHandler: undefined,\n yDoc\n };\n this.sessions.set(sessionId, session);\n const localYDocUpdateHandler = (update, origin) => {\n if (origin === UPDATE_ORIGIN_REMOTE) {\n return;\n }\n const lastRevisionId = session.lastRevisionId;\n if (lastRevisionId == null) {\n this.logger.error(\"Cannot publish document update before initial load is complete. The local state will remain inconsistent.\", {\n docId: documentId\n });\n return;\n }\n const publishChannelId = getDocumentPublishChannelId(documentId);\n const editId = generateId();\n void this.eventService.publish(publishChannelId, {\n clientId: session.clientId,\n editId,\n yjsUpdate: {\n data: Base64.fromUint8Array(update)\n }\n }).catch(error => {\n this.logger.error(\"Failed to publish document update\", error, {\n docId: documentId\n });\n });\n };\n session.localYDocUpdateHandler = localYDocUpdateHandler;\n yDoc.on(\"update\", localYDocUpdateHandler);\n onStatusChange({\n load: DocumentLoadStatus.LOADING\n });\n const channelId = getDocumentUpdatesChannelId(documentId);\n this.eventService.subscribe(channelId, message => {\n if (session.localYDocUpdateHandler !== localYDocUpdateHandler) {\n return;\n }\n this.handleDocumentUpdateMessage(session, message, onStatusChange);\n }, () => ({\n clientId: session.clientId,\n lastRevisionId: session.lastRevisionId?.toString()\n })).then(subscriptionId => {\n if (session.localYDocUpdateHandler === localYDocUpdateHandler) {\n session.documentSubscriptionId = subscriptionId;\n } else {\n this.eventService.unsubscribe(subscriptionId);\n this.sessions.delete(this.getSessionId(documentId));\n }\n }).catch(e => {\n if (session.localYDocUpdateHandler === localYDocUpdateHandler) {\n const error = new Error(\"Failed to setup document data subscription\", {\n cause: e\n });\n onStatusChange({\n error,\n load: DocumentLoadStatus.ERROR\n });\n } else {\n this.logger.warn(\"Document data subscription error after subscription was closed\", {\n docId: documentId,\n error: e\n });\n }\n });\n return {\n clientId: session.clientId,\n documentId: session.documentId\n };\n }\n stopDocumentSync(session) {\n const sessionId = this.getSessionId(session.documentId);\n const internalSession = this.sessions.get(sessionId);\n if (internalSession == null) {\n this.logger.warn(\"Attempted to stop sync for unknown session\", {\n documentId: session.documentId,\n clientId: session.clientId\n });\n return;\n }\n if (internalSession.localYDocUpdateHandler != null) {\n internalSession.yDoc.off(\"update\", internalSession.localYDocUpdateHandler);\n internalSession.localYDocUpdateHandler = undefined;\n }\n if (internalSession.documentSubscriptionId) {\n this.eventService.unsubscribe(internalSession.documentSubscriptionId);\n this.sessions.delete(sessionId);\n }\n }\n handleDocumentUpdateMessage(session, message, onStatusChange) {\n switch (message.type) {\n case \"error\":\n const {\n args,\n code,\n errorInstanceId\n } = message;\n this.logger.error(\"Received document update error message\", {\n docId: session.documentId,\n code,\n errorInstanceId,\n args\n });\n onStatusChange({\n error: new Error(`Subscription in error state [${errorInstanceId}]`, {\n cause: message\n }),\n load: DocumentLoadStatus.ERROR\n });\n break;\n case \"update\":\n const {\n baseRevisionId,\n clientId,\n revisionId,\n update\n } = message;\n const data = update != null && typeof update.data === \"string\" ? Base64.toUint8Array(update.data) : undefined;\n const messageDetail = {\n baseRevisionId,\n clientId,\n revisionId,\n updateSize: data?.byteLength ?? 0\n };\n\n // FIXME: the typescript generators for api types come out as string, hard to be clear that they are numbers.\n if (session.lastRevisionId != null && Number(baseRevisionId) !== session.lastRevisionId) {\n this.logger.error(\"Got unexpected update for baseRevisionId\", {\n docId: session.documentId,\n lastRevisionId: session.lastRevisionId,\n message: messageDetail\n });\n return;\n }\n this.logger.debug(\"Applying remote Y.js update\", {\n docId: session.documentId,\n lastRevisionId: session.lastRevisionId,\n message: messageDetail\n });\n session.lastRevisionId = Number(revisionId);\n if (data != null) {\n y.applyUpdate(session.yDoc, data, UPDATE_ORIGIN_REMOTE);\n }\n onStatusChange({\n load: DocumentLoadStatus.LOADED\n });\n break;\n default:\n message;\n const {\n type\n } = message;\n justOnce(`unknown-collab-update-type:${type}`, () => {\n this.logger.warn(\"Received unknown DocumentUpdateMessage type. This is only warned the first occurrence.\", {\n docId: session.documentId,\n updateType: type\n });\n });\n break;\n }\n }\n getSessionId(documentId) {\n return documentId;\n }\n}\nexport function createFoundryEventService(app, cometd) {\n return new FoundryEventService(app, cometd);\n}"]}
|