@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.
@@ -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
+
@@ -0,0 +1,5 @@
1
+
2
+ > @palantir/pack.state.foundry-event@0.0.1-beta.1 transpileBrowser /home/runner/work/pack/pack/packages/state/foundry-event
3
+ > monorepo-transpile -f esm -m bundle -t browser
4
+
5
+ 👍
@@ -0,0 +1,5 @@
1
+
2
+ > @palantir/pack.state.foundry-event@0.0.1-beta.1 transpileCjs /home/runner/work/pack/pack/packages/state/foundry-event
3
+ > monorepo-transpile -f cjs -m bundle -t node
4
+
5
+ 👍
@@ -0,0 +1,5 @@
1
+
2
+ > @palantir/pack.state.foundry-event@0.0.1-beta.1 transpileEsm /home/runner/work/pack/pack/packages/state/foundry-event
3
+ > monorepo-transpile -f esm -m bundle -t node
4
+
5
+ 👍
@@ -0,0 +1,5 @@
1
+
2
+ > @palantir/pack.state.foundry-event@0.0.1-beta.1 transpileTypes /home/runner/work/pack/pack/packages/state/foundry-event
3
+ > monorepo-transpile -f esm -m types -t node
4
+
5
+ 👍
@@ -0,0 +1,4 @@
1
+
2
+ > @palantir/pack.state.foundry-event@0.0.1-beta.1 typecheck /home/runner/work/pack/pack/packages/state/foundry-event
3
+ > tsc --noEmit --emitDeclarationOnly false
4
+
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}"]}