@agent-native/core 0.116.0 → 0.117.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +15 -0
- package/corpus/core/package.json +4 -1
- package/corpus/core/src/client/analytics.ts +7 -0
- package/corpus/core/src/client/use-db-sync.ts +354 -15
- package/corpus/core/src/collab/client.ts +26 -2
- package/corpus/core/src/db/ddl-guard.ts +49 -10
- package/corpus/core/src/deploy/build.ts +25 -2
- package/corpus/core/src/realtime-protocol.ts +92 -0
- package/corpus/core/src/server/core-routes-plugin.ts +7 -0
- package/corpus/core/src/server/poll-events.ts +29 -8
- package/corpus/core/src/server/poll.ts +1123 -865
- package/corpus/core/src/server/realtime-token.ts +106 -0
- package/corpus/core/src/server/sentry-config.ts +40 -0
- package/corpus/core/src/server/short-lived-token.ts +156 -0
- package/corpus/core/src/server/sse.ts +15 -0
- package/corpus/core/src/server/ssr-handler.ts +9 -3
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +12 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +566 -299
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +14 -0
- package/corpus/templates/content/app/i18n-data.ts +90 -0
- package/dist/client/analytics.d.ts +10 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +1 -1
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +315 -15
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +26 -2
- package/dist/collab/client.js.map +1 -1
- package/dist/db/ddl-guard.d.ts +7 -2
- package/dist/db/ddl-guard.d.ts.map +1 -1
- package/dist/db/ddl-guard.js +21 -9
- package/dist/db/ddl-guard.js.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +25 -2
- package/dist/deploy/build.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +6 -6
- package/dist/realtime-protocol.d.ts +40 -0
- package/dist/realtime-protocol.d.ts.map +1 -0
- package/dist/realtime-protocol.js +69 -0
- package/dist/realtime-protocol.js.map +1 -0
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +3 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/poll-events.d.ts +7 -1
- package/dist/server/poll-events.d.ts.map +1 -1
- package/dist/server/poll-events.js +24 -7
- package/dist/server/poll-events.js.map +1 -1
- package/dist/server/poll.d.ts +223 -42
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +849 -737
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-token.d.ts +34 -0
- package/dist/server/realtime-token.d.ts.map +1 -0
- package/dist/server/realtime-token.js +85 -0
- package/dist/server/realtime-token.js.map +1 -0
- package/dist/server/sentry-config.d.ts +12 -0
- package/dist/server/sentry-config.d.ts.map +1 -1
- package/dist/server/sentry-config.js +34 -0
- package/dist/server/sentry-config.js.map +1 -1
- package/dist/server/short-lived-token.d.ts +46 -0
- package/dist/server/short-lived-token.d.ts.map +1 -1
- package/dist/server/short-lived-token.js +104 -0
- package/dist/server/short-lived-token.js.map +1 -1
- package/dist/server/sse.d.ts +1 -1
- package/dist/server/sse.d.ts.map +1 -1
- package/dist/server/sse.js +4 -0
- package/dist/server/sse.js.map +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +5 -3
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +4 -1
- package/src/client/analytics.ts +7 -0
- package/src/client/use-db-sync.ts +354 -15
- package/src/collab/client.ts +26 -2
- package/src/db/ddl-guard.ts +49 -10
- package/src/deploy/build.ts +25 -2
- package/src/realtime-protocol.ts +92 -0
- package/src/server/core-routes-plugin.ts +7 -0
- package/src/server/poll-events.ts +29 -8
- package/src/server/poll.ts +1123 -865
- package/src/server/realtime-token.ts +106 -0
- package/src/server/sentry-config.ts +40 -0
- package/src/server/short-lived-token.ts +156 -0
- package/src/server/sse.ts +15 -0
- package/src/server/ssr-handler.ts +9 -3
package/src/db/ddl-guard.ts
CHANGED
|
@@ -46,8 +46,14 @@ const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
46
46
|
export async function pgTableExists(
|
|
47
47
|
table: string,
|
|
48
48
|
injectedClient?: DbExec,
|
|
49
|
+
dialectIsPostgres?: boolean,
|
|
49
50
|
): Promise<boolean> {
|
|
50
|
-
|
|
51
|
+
// The dialect override travels with an injected client: a multi-app process
|
|
52
|
+
// (the hosted Realtime Gateway) probes a per-app Postgres DB even when its
|
|
53
|
+
// own process-global DB is absent or SQLite.
|
|
54
|
+
if (!(dialectIsPostgres ?? isPostgres()) || !PLAIN_IDENTIFIER.test(table)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
51
57
|
const client = injectedClient ?? getDbExec();
|
|
52
58
|
try {
|
|
53
59
|
const { rows } = await client.execute({
|
|
@@ -106,8 +112,14 @@ export async function pgColumnExists(
|
|
|
106
112
|
export async function pgIndexExists(
|
|
107
113
|
indexName: string,
|
|
108
114
|
injectedClient?: DbExec,
|
|
115
|
+
dialectIsPostgres?: boolean,
|
|
109
116
|
): Promise<boolean> {
|
|
110
|
-
if (
|
|
117
|
+
if (
|
|
118
|
+
!(dialectIsPostgres ?? isPostgres()) ||
|
|
119
|
+
!PLAIN_IDENTIFIER.test(indexName)
|
|
120
|
+
) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
111
123
|
const client = injectedClient ?? getDbExec();
|
|
112
124
|
try {
|
|
113
125
|
const { rows } = await client.execute({
|
|
@@ -158,10 +170,17 @@ export async function ensureSchemaObject(options: {
|
|
|
158
170
|
lockTimeout?: string;
|
|
159
171
|
/** Injectable client for tests. */
|
|
160
172
|
injectedClient?: DbExec;
|
|
173
|
+
/** Dialect override for injected per-app clients; defaults to the global. */
|
|
174
|
+
dialectIsPostgres?: boolean;
|
|
161
175
|
}): Promise<boolean> {
|
|
162
|
-
const { probe, ddl, label, lockTimeout, injectedClient } =
|
|
176
|
+
const { probe, ddl, label, lockTimeout, injectedClient, dialectIsPostgres } =
|
|
177
|
+
options;
|
|
163
178
|
if (await probe()) return false;
|
|
164
|
-
const ran = await runGuardedDdl(ddl, {
|
|
179
|
+
const ran = await runGuardedDdl(ddl, {
|
|
180
|
+
lockTimeout,
|
|
181
|
+
injectedClient,
|
|
182
|
+
dialectIsPostgres,
|
|
183
|
+
});
|
|
165
184
|
if (ran) return true;
|
|
166
185
|
// The DDL was swallowed by a lock-timeout. The object is virtually always
|
|
167
186
|
// already correct by the time a contended boot retries (a concurrent
|
|
@@ -185,14 +204,20 @@ export async function ensureSchemaObject(options: {
|
|
|
185
204
|
export async function ensureTableExists(
|
|
186
205
|
table: string,
|
|
187
206
|
createSql: string,
|
|
188
|
-
options: {
|
|
207
|
+
options: {
|
|
208
|
+
lockTimeout?: string;
|
|
209
|
+
injectedClient?: DbExec;
|
|
210
|
+
dialectIsPostgres?: boolean;
|
|
211
|
+
} = {},
|
|
189
212
|
): Promise<boolean> {
|
|
190
213
|
return ensureSchemaObject({
|
|
191
|
-
probe: () =>
|
|
214
|
+
probe: () =>
|
|
215
|
+
pgTableExists(table, options.injectedClient, options.dialectIsPostgres),
|
|
192
216
|
ddl: createSql,
|
|
193
217
|
label: `table ${table}`,
|
|
194
218
|
lockTimeout: options.lockTimeout,
|
|
195
219
|
injectedClient: options.injectedClient,
|
|
220
|
+
dialectIsPostgres: options.dialectIsPostgres,
|
|
196
221
|
});
|
|
197
222
|
}
|
|
198
223
|
|
|
@@ -222,14 +247,24 @@ export async function ensureColumnExists(
|
|
|
222
247
|
export async function ensureIndexExists(
|
|
223
248
|
indexName: string,
|
|
224
249
|
createIndexSql: string,
|
|
225
|
-
options: {
|
|
250
|
+
options: {
|
|
251
|
+
lockTimeout?: string;
|
|
252
|
+
injectedClient?: DbExec;
|
|
253
|
+
dialectIsPostgres?: boolean;
|
|
254
|
+
} = {},
|
|
226
255
|
): Promise<boolean> {
|
|
227
256
|
return ensureSchemaObject({
|
|
228
|
-
probe: () =>
|
|
257
|
+
probe: () =>
|
|
258
|
+
pgIndexExists(
|
|
259
|
+
indexName,
|
|
260
|
+
options.injectedClient,
|
|
261
|
+
options.dialectIsPostgres,
|
|
262
|
+
),
|
|
229
263
|
ddl: createIndexSql,
|
|
230
264
|
label: `index ${indexName}`,
|
|
231
265
|
lockTimeout: options.lockTimeout,
|
|
232
266
|
injectedClient: options.injectedClient,
|
|
267
|
+
dialectIsPostgres: options.dialectIsPostgres,
|
|
233
268
|
});
|
|
234
269
|
}
|
|
235
270
|
|
|
@@ -263,10 +298,14 @@ export function isLockTimeoutError(err: unknown): boolean {
|
|
|
263
298
|
*/
|
|
264
299
|
export async function runGuardedDdl(
|
|
265
300
|
ddl: string,
|
|
266
|
-
options: {
|
|
301
|
+
options: {
|
|
302
|
+
lockTimeout?: string;
|
|
303
|
+
injectedClient?: DbExec;
|
|
304
|
+
dialectIsPostgres?: boolean;
|
|
305
|
+
} = {},
|
|
267
306
|
): Promise<boolean> {
|
|
268
307
|
const client = options.injectedClient ?? getDbExec();
|
|
269
|
-
if (!isPostgres()) {
|
|
308
|
+
if (!(options.dialectIsPostgres ?? isPostgres())) {
|
|
270
309
|
await client.execute(ddl);
|
|
271
310
|
return true;
|
|
272
311
|
}
|
package/src/deploy/build.ts
CHANGED
|
@@ -951,6 +951,26 @@ function getSentryClientConfigScript() {
|
|
|
951
951
|
);
|
|
952
952
|
}
|
|
953
953
|
|
|
954
|
+
function getRealtimeClientConfigScript() {
|
|
955
|
+
// MUST stay byte-for-byte consistent with resolveRealtimeClientConfig in
|
|
956
|
+
// server/sentry-config.ts (worker bundles a string copy; it can't import it).
|
|
957
|
+
// Fail closed: require BOTH hosted transport AND an explicit gateway URL — no
|
|
958
|
+
// production default, since this ships into the CDN-cached shell.
|
|
959
|
+
const env = globalThis.process?.env || {};
|
|
960
|
+
if (firstNonEmpty(env.AGENT_NATIVE_REALTIME_TRANSPORT) !== "hosted") {
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
const gatewayBaseUrl = firstNonEmpty(env.AGENT_NATIVE_REALTIME_GATEWAY_URL);
|
|
964
|
+
if (!gatewayBaseUrl) return null;
|
|
965
|
+
const config = { realtime: { transport: "hosted", gatewayBaseUrl } };
|
|
966
|
+
return (
|
|
967
|
+
'<script data-agent-native-realtime-config>' +
|
|
968
|
+
'window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,' +
|
|
969
|
+
JSON.stringify(config) +
|
|
970
|
+
");</script>"
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
|
|
954
974
|
function injectHeadScript(html, script) {
|
|
955
975
|
if (!script) return html;
|
|
956
976
|
const headCloseIdx = html.indexOf("</head>");
|
|
@@ -1102,7 +1122,10 @@ function applyImmutableAssetCacheHeaders(response, request) {
|
|
|
1102
1122
|
}
|
|
1103
1123
|
|
|
1104
1124
|
async function rewriteMountedResponse(response, basePath, pathname, request) {
|
|
1105
|
-
const
|
|
1125
|
+
const clientConfigScript =
|
|
1126
|
+
[getSentryClientConfigScript(), getRealtimeClientConfigScript()]
|
|
1127
|
+
.filter(Boolean)
|
|
1128
|
+
.join("") || null;
|
|
1106
1129
|
const headers = new Headers(response.headers);
|
|
1107
1130
|
applyDefaultSsrCacheHeader(headers, response.status, pathname);
|
|
1108
1131
|
applyDefaultSpeculationRulesHeader(headers, response.status, basePath);
|
|
@@ -1129,7 +1152,7 @@ async function rewriteMountedResponse(response, basePath, pathname, request) {
|
|
|
1129
1152
|
prefixMountedHtml(html, basePath),
|
|
1130
1153
|
defaultSocialImageUrl(request, basePath),
|
|
1131
1154
|
),
|
|
1132
|
-
|
|
1155
|
+
clientConfigScript,
|
|
1133
1156
|
),
|
|
1134
1157
|
{
|
|
1135
1158
|
status: response.status,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realtime SSE wire protocol — shared by the server SSE handlers (framework
|
|
3
|
+
* in-process path + the hosted Realtime Gateway) and the client transport.
|
|
4
|
+
*
|
|
5
|
+
* Pure constants, types, and parsers only — NO h3/node/browser deps — so both
|
|
6
|
+
* the Node server and the browser client import it.
|
|
7
|
+
*
|
|
8
|
+
* Frame taxonomy:
|
|
9
|
+
* - CHANGE / batch frames ride the default (unnamed) SSE `message` event and
|
|
10
|
+
* are parsed by the client's existing `normalizeEventPayload`. Unchanged.
|
|
11
|
+
* - CONTROL frames (handshake, token rotation) ride NAMED SSE events, so
|
|
12
|
+
* `EventSource.addEventListener(name)` routes them separately and an older
|
|
13
|
+
* client that only reads `onmessage` silently ignores them (backward safe).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const REALTIME_PROTOCOL_VERSION = 1;
|
|
17
|
+
|
|
18
|
+
/** Named SSE event carrying the one-time handshake ({@link RealtimeHandshake}). */
|
|
19
|
+
export const REALTIME_SSE_HANDSHAKE_EVENT = "handshake";
|
|
20
|
+
/** Named SSE event carrying a rotated subscribe token ({@link RealtimeTokenFrame}). */
|
|
21
|
+
export const REALTIME_SSE_TOKEN_EVENT = "token";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Capability advertised by the hosted gateway: awareness/presence is NOT
|
|
25
|
+
* forwarded (it lives only on the in-process emitter, never in `sync_events`),
|
|
26
|
+
* so the collab client must keep its own presence cadence. The framework's
|
|
27
|
+
* in-process SSE path forwards awareness and therefore sends no handshake — the
|
|
28
|
+
* client treats an absent handshake as "all capabilities present."
|
|
29
|
+
*/
|
|
30
|
+
export const REALTIME_CAP_NO_AWARENESS = "no-awareness";
|
|
31
|
+
|
|
32
|
+
export interface RealtimeHandshake {
|
|
33
|
+
protocol: number;
|
|
34
|
+
capabilities: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RealtimeTokenFrame {
|
|
38
|
+
token: string;
|
|
39
|
+
/** ISO expiry, informational; the client re-mints/rotates before this. */
|
|
40
|
+
expiresAt?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function buildHandshakeFrame(
|
|
44
|
+
capabilities: readonly string[] = [],
|
|
45
|
+
): RealtimeHandshake {
|
|
46
|
+
return {
|
|
47
|
+
protocol: REALTIME_PROTOCOL_VERSION,
|
|
48
|
+
capabilities: [...capabilities],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function parseHandshakeFrame(data: string): RealtimeHandshake | null {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(data);
|
|
55
|
+
if (
|
|
56
|
+
parsed &&
|
|
57
|
+
typeof parsed === "object" &&
|
|
58
|
+
typeof parsed.protocol === "number" &&
|
|
59
|
+
Array.isArray(parsed.capabilities)
|
|
60
|
+
) {
|
|
61
|
+
return {
|
|
62
|
+
protocol: parsed.protocol,
|
|
63
|
+
capabilities: parsed.capabilities.filter(
|
|
64
|
+
(c: unknown): c is string => typeof c === "string",
|
|
65
|
+
),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// fall through
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function parseTokenFrame(data: string): RealtimeTokenFrame | null {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(data);
|
|
77
|
+
if (
|
|
78
|
+
parsed &&
|
|
79
|
+
typeof parsed === "object" &&
|
|
80
|
+
typeof parsed.token === "string"
|
|
81
|
+
) {
|
|
82
|
+
return {
|
|
83
|
+
token: parsed.token,
|
|
84
|
+
expiresAt:
|
|
85
|
+
typeof parsed.expiresAt === "string" ? parsed.expiresAt : undefined,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// fall through
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
@@ -170,6 +170,7 @@ import { handleIdentitySso } from "./identity-sso.js";
|
|
|
170
170
|
import { createOpenRouteHandler } from "./open-route.js";
|
|
171
171
|
import { createPollEventsHandler } from "./poll-events.js";
|
|
172
172
|
import { createPollHandler } from "./poll.js";
|
|
173
|
+
import { createRealtimeTokenHandler } from "./realtime-token.js";
|
|
173
174
|
import { runWithRequestContext } from "./request-context.js";
|
|
174
175
|
import {
|
|
175
176
|
findUnsupportedScopedKeyNames,
|
|
@@ -1414,6 +1415,12 @@ export function createCoreRoutesPlugin(
|
|
|
1414
1415
|
// Polling
|
|
1415
1416
|
getH3App(nitroApp).use(`${P}/poll`, createPollHandler());
|
|
1416
1417
|
|
|
1418
|
+
// Realtime subscribe-token mint (hosted gateway path)
|
|
1419
|
+
getH3App(nitroApp).use(
|
|
1420
|
+
`${P}/realtime-token`,
|
|
1421
|
+
createRealtimeTokenHandler(),
|
|
1422
|
+
);
|
|
1423
|
+
|
|
1417
1424
|
// SSE
|
|
1418
1425
|
if (!options.disableSSE) {
|
|
1419
1426
|
for (const route of resolveFrameworkSseRoutes(options.sseRoute)) {
|
|
@@ -7,10 +7,11 @@ import {
|
|
|
7
7
|
} from "../collab/awareness.js";
|
|
8
8
|
import { getSession } from "./auth.js";
|
|
9
9
|
import {
|
|
10
|
+
type AppSyncState,
|
|
10
11
|
canSeeChangeForUser,
|
|
11
|
-
getPollEmitter,
|
|
12
|
-
POLL_CHANGE_EVENT,
|
|
13
12
|
type ChangeEvent,
|
|
13
|
+
getDefaultAppSyncState,
|
|
14
|
+
POLL_CHANGE_EVENT,
|
|
14
15
|
} from "./poll.js";
|
|
15
16
|
|
|
16
17
|
export function canSeeAwarenessChangeForUser(
|
|
@@ -37,8 +38,15 @@ export function canSeeAwarenessChangeForUser(
|
|
|
37
38
|
* connected peers receive cursor moves push-style instead of waiting for
|
|
38
39
|
* the next poll cycle. Polling fallback keeps working — cursors degrade
|
|
39
40
|
* gracefully to poll cadence without SSE.
|
|
41
|
+
*
|
|
42
|
+
* `state` defaults to the process-wide instance so self-hosted apps are
|
|
43
|
+
* unchanged; the hosted gateway passes a per-app instance to fan out that
|
|
44
|
+
* app's events (awareness stays on the process-global emitter and is a v0
|
|
45
|
+
* gateway Non-Goal).
|
|
40
46
|
*/
|
|
41
|
-
export function createPollEventsHandler(
|
|
47
|
+
export function createPollEventsHandler(
|
|
48
|
+
state: AppSyncState = getDefaultAppSyncState(),
|
|
49
|
+
) {
|
|
42
50
|
return defineEventHandler(async (event) => {
|
|
43
51
|
const session = await getSession(event).catch(() => null);
|
|
44
52
|
if (!session?.email) {
|
|
@@ -60,7 +68,9 @@ export function createPollEventsHandler() {
|
|
|
60
68
|
|
|
61
69
|
const push = (change: ChangeEvent) => {
|
|
62
70
|
if (closed) return;
|
|
63
|
-
if (!canSeeChangeForUser(change, session.email, session.orgId))
|
|
71
|
+
if (!state.canSeeChangeForUser(change, session.email, session.orgId)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
64
74
|
safePush(JSON.stringify(change));
|
|
65
75
|
};
|
|
66
76
|
|
|
@@ -74,13 +84,24 @@ export function createPollEventsHandler() {
|
|
|
74
84
|
safePush(JSON.stringify(change));
|
|
75
85
|
};
|
|
76
86
|
|
|
77
|
-
getPollEmitter().on(POLL_CHANGE_EVENT, push);
|
|
78
|
-
|
|
87
|
+
state.getPollEmitter().on(POLL_CHANGE_EVENT, push);
|
|
88
|
+
// Awareness lives ONLY on the process-global emitter and is filtered via the
|
|
89
|
+
// module-level (default-state) access path — both untenable in a multi-app
|
|
90
|
+
// gateway process, where they'd leak another app's presence/document id and
|
|
91
|
+
// resolve access against the wrong tenant. Fail closed: only the default
|
|
92
|
+
// (in-process) instance forwards awareness. The gateway advertises
|
|
93
|
+
// `no-awareness` so collab keeps its own presence cadence.
|
|
94
|
+
const forwardAwareness = state === getDefaultAppSyncState();
|
|
95
|
+
if (forwardAwareness) {
|
|
96
|
+
getAwarenessEmitter().on(AWARENESS_CHANGE_EVENT, pushAwareness);
|
|
97
|
+
}
|
|
79
98
|
|
|
80
99
|
stream.onClosed(() => {
|
|
81
100
|
closed = true;
|
|
82
|
-
getPollEmitter().off(POLL_CHANGE_EVENT, push);
|
|
83
|
-
|
|
101
|
+
state.getPollEmitter().off(POLL_CHANGE_EVENT, push);
|
|
102
|
+
if (forwardAwareness) {
|
|
103
|
+
getAwarenessEmitter().off(AWARENESS_CHANGE_EVENT, pushAwareness);
|
|
104
|
+
}
|
|
84
105
|
});
|
|
85
106
|
|
|
86
107
|
return stream.send();
|