@syncular/server-hono 0.0.4-25 → 0.0.6-100
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/README.md +6 -1
- package/dist/console/gateway.d.ts +3 -1
- package/dist/console/gateway.d.ts.map +1 -1
- package/dist/console/gateway.js +227 -42
- package/dist/console/gateway.js.map +1 -1
- package/dist/console/index.d.ts +2 -0
- package/dist/console/index.d.ts.map +1 -1
- package/dist/console/index.js +2 -0
- package/dist/console/index.js.map +1 -1
- package/dist/console/routes.d.ts +3 -97
- package/dist/console/routes.d.ts.map +1 -1
- package/dist/console/routes.js +516 -81
- package/dist/console/routes.js.map +1 -1
- package/dist/console/schemas.d.ts +29 -0
- package/dist/console/schemas.d.ts.map +1 -1
- package/dist/console/schemas.js +22 -0
- package/dist/console/schemas.js.map +1 -1
- package/dist/console/types.d.ts +175 -0
- package/dist/console/types.d.ts.map +1 -0
- package/dist/console/types.js +2 -0
- package/dist/console/types.js.map +1 -0
- package/dist/console/ui.d.ts +38 -0
- package/dist/console/ui.d.ts.map +1 -0
- package/dist/console/ui.js +43 -0
- package/dist/console/ui.js.map +1 -0
- package/dist/create-server.d.ts +17 -34
- package/dist/create-server.d.ts.map +1 -1
- package/dist/create-server.js +26 -26
- package/dist/create-server.js.map +1 -1
- package/dist/proxy/connection-manager.d.ts +3 -3
- package/dist/proxy/connection-manager.d.ts.map +1 -1
- package/dist/proxy/routes.d.ts +4 -4
- package/dist/proxy/routes.d.ts.map +1 -1
- package/dist/proxy/routes.js +1 -1
- package/dist/routes.d.ts +33 -9
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +153 -70
- package/dist/routes.js.map +1 -1
- package/package.json +21 -6
- package/src/__tests__/blob-routes.test.ts +424 -0
- package/src/__tests__/console-gateway-live-routes.test.ts +54 -3
- package/src/__tests__/console-routes.test.ts +161 -7
- package/src/__tests__/console-ui.test.ts +114 -0
- package/src/__tests__/create-server.test.ts +233 -10
- package/src/__tests__/pull-chunk-storage.test.ts +6 -2
- package/src/__tests__/realtime-bridge.test.ts +6 -2
- package/src/__tests__/sync-rate-limit-routing.test.ts +6 -2
- package/src/console/gateway.ts +286 -54
- package/src/console/index.ts +2 -0
- package/src/console/routes.ts +663 -199
- package/src/console/schemas.ts +29 -0
- package/src/console/types.ts +185 -0
- package/src/console/ui.ts +100 -0
- package/src/create-server.ts +56 -53
- package/src/proxy/connection-manager.ts +3 -3
- package/src/proxy/routes.ts +4 -4
- package/src/routes.ts +225 -96
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
2
|
+
import { createDatabase } from '@syncular/core';
|
|
2
3
|
import {
|
|
3
4
|
createServerHandler,
|
|
4
5
|
ensureSyncSchema,
|
|
@@ -6,7 +7,7 @@ import {
|
|
|
6
7
|
} from '@syncular/server';
|
|
7
8
|
import { Hono } from 'hono';
|
|
8
9
|
import type { Kysely } from 'kysely';
|
|
9
|
-
import {
|
|
10
|
+
import { createBunSqliteDialect } from '../../../dialect-bun-sqlite/src';
|
|
10
11
|
import { createSqliteServerDialect } from '../../../server-dialect-sqlite/src';
|
|
11
12
|
import { resetRateLimitStore } from '../rate-limit';
|
|
12
13
|
import { createSyncRoutes } from '../routes';
|
|
@@ -31,7 +32,10 @@ describe('createSyncRoutes rate limit routing', () => {
|
|
|
31
32
|
const dialect = createSqliteServerDialect();
|
|
32
33
|
|
|
33
34
|
beforeEach(async () => {
|
|
34
|
-
db =
|
|
35
|
+
db = createDatabase<ServerDb>({
|
|
36
|
+
dialect: createBunSqliteDialect({ path: ':memory:' }),
|
|
37
|
+
family: 'sqlite',
|
|
38
|
+
});
|
|
35
39
|
await ensureSyncSchema(db, dialect);
|
|
36
40
|
|
|
37
41
|
await db.schema
|
package/src/console/gateway.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { cors } from 'hono/cors';
|
|
|
4
4
|
import type { UpgradeWebSocket } from 'hono/ws';
|
|
5
5
|
import { describeRoute, resolver, validator as zValidator } from 'hono-openapi';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import type { ConsoleAuthResult } from './routes';
|
|
8
7
|
import type {
|
|
9
8
|
ConsoleApiKey,
|
|
10
9
|
ConsoleApiKeyBulkRevokeResponse,
|
|
@@ -61,6 +60,7 @@ import {
|
|
|
61
60
|
TimeseriesQuerySchema,
|
|
62
61
|
TimeseriesStatsResponseSchema,
|
|
63
62
|
} from './schemas';
|
|
63
|
+
import type { ConsoleAuthResult } from './types';
|
|
64
64
|
|
|
65
65
|
export interface ConsoleGatewayInstance {
|
|
66
66
|
instanceId: string;
|
|
@@ -71,9 +71,11 @@ export interface ConsoleGatewayInstance {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
interface ConsoleGatewayDownstreamSocket {
|
|
74
|
+
onopen?: ((event: Event) => void) | null;
|
|
74
75
|
onmessage: ((event: MessageEvent) => void) | null;
|
|
75
76
|
onerror: ((event: Event) => void) | null;
|
|
76
77
|
close: () => void;
|
|
78
|
+
send?: (data: string) => void;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
export interface CreateConsoleGatewayRoutesOptions {
|
|
@@ -723,35 +725,18 @@ function resolveForwardAuthorization(args: {
|
|
|
723
725
|
if (header) {
|
|
724
726
|
return header;
|
|
725
727
|
}
|
|
726
|
-
const queryToken = args.c.req.query('token')?.trim();
|
|
727
|
-
if (queryToken) {
|
|
728
|
-
return `Bearer ${queryToken}`;
|
|
729
|
-
}
|
|
730
728
|
return null;
|
|
731
729
|
}
|
|
732
730
|
|
|
733
|
-
function
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if (
|
|
738
|
-
return
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
const authHeader = args.c.req.header('Authorization')?.trim();
|
|
742
|
-
if (authHeader?.startsWith('Bearer ')) {
|
|
743
|
-
const token = authHeader.slice(7).trim();
|
|
744
|
-
if (token.length > 0) {
|
|
745
|
-
return token;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
const queryToken = args.c.req.query('token')?.trim();
|
|
750
|
-
if (queryToken) {
|
|
751
|
-
return queryToken;
|
|
731
|
+
function parseBearerToken(
|
|
732
|
+
authHeader: string | null | undefined
|
|
733
|
+
): string | null {
|
|
734
|
+
const value = authHeader?.trim();
|
|
735
|
+
if (!value?.startsWith('Bearer ')) {
|
|
736
|
+
return null;
|
|
752
737
|
}
|
|
753
|
-
|
|
754
|
-
return null;
|
|
738
|
+
const token = value.slice(7).trim();
|
|
739
|
+
return token.length > 0 ? token : null;
|
|
755
740
|
}
|
|
756
741
|
|
|
757
742
|
async function fetchDownstreamJson<T>(args: {
|
|
@@ -1081,7 +1066,15 @@ export function createConsoleGatewayRoutes(
|
|
|
1081
1066
|
cors({
|
|
1082
1067
|
origin: corsOrigins === '*' ? '*' : corsOrigins,
|
|
1083
1068
|
allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
|
|
1084
|
-
allowHeaders: [
|
|
1069
|
+
allowHeaders: [
|
|
1070
|
+
'Content-Type',
|
|
1071
|
+
'Authorization',
|
|
1072
|
+
'X-Syncular-Transport-Path',
|
|
1073
|
+
'Baggage',
|
|
1074
|
+
'Sentry-Trace',
|
|
1075
|
+
'Traceparent',
|
|
1076
|
+
'Tracestate',
|
|
1077
|
+
],
|
|
1085
1078
|
credentials: true,
|
|
1086
1079
|
})
|
|
1087
1080
|
);
|
|
@@ -3028,14 +3021,16 @@ export function createConsoleGatewayRoutes(
|
|
|
3028
3021
|
WebSocketLike,
|
|
3029
3022
|
{
|
|
3030
3023
|
downstreamSockets: ConsoleGatewayDownstreamSocket[];
|
|
3031
|
-
heartbeatInterval: ReturnType<typeof setInterval
|
|
3024
|
+
heartbeatInterval: ReturnType<typeof setInterval> | null;
|
|
3025
|
+
authTimeout: ReturnType<typeof setTimeout> | null;
|
|
3026
|
+
isAuthenticated: boolean;
|
|
3032
3027
|
}
|
|
3033
3028
|
>();
|
|
3034
3029
|
|
|
3035
3030
|
routes.get(
|
|
3036
3031
|
'/events/live',
|
|
3037
3032
|
upgradeWebSocket(async (c) => {
|
|
3038
|
-
const
|
|
3033
|
+
const initialAuth = await options.authenticate(c);
|
|
3039
3034
|
const partitionId = c.req.query('partitionId')?.trim() || undefined;
|
|
3040
3035
|
const replaySince = c.req.query('since')?.trim() || undefined;
|
|
3041
3036
|
const replayLimitRaw = c.req.query('replayLimit');
|
|
@@ -3054,10 +3049,43 @@ export function createConsoleGatewayRoutes(
|
|
|
3054
3049
|
},
|
|
3055
3050
|
});
|
|
3056
3051
|
|
|
3052
|
+
const authenticateWithBearer = async (
|
|
3053
|
+
token: string
|
|
3054
|
+
): Promise<ConsoleAuthResult | null> => {
|
|
3055
|
+
const trimmedToken = token.trim();
|
|
3056
|
+
if (!trimmedToken) {
|
|
3057
|
+
return null;
|
|
3058
|
+
}
|
|
3059
|
+
const authContext = {
|
|
3060
|
+
req: {
|
|
3061
|
+
header: (name: string) =>
|
|
3062
|
+
name === 'Authorization' ? `Bearer ${trimmedToken}` : undefined,
|
|
3063
|
+
query: () => undefined,
|
|
3064
|
+
},
|
|
3065
|
+
} as unknown as Context;
|
|
3066
|
+
return options.authenticate(authContext);
|
|
3067
|
+
};
|
|
3068
|
+
|
|
3069
|
+
const closeUnauthenticated = (ws: WebSocketLike) => {
|
|
3070
|
+
try {
|
|
3071
|
+
ws.send(
|
|
3072
|
+
JSON.stringify({ type: 'error', message: 'UNAUTHENTICATED' })
|
|
3073
|
+
);
|
|
3074
|
+
} catch {
|
|
3075
|
+
// no-op
|
|
3076
|
+
}
|
|
3077
|
+
ws.close(4001, 'Unauthenticated');
|
|
3078
|
+
};
|
|
3079
|
+
|
|
3057
3080
|
const cleanup = (ws: WebSocketLike) => {
|
|
3058
3081
|
const state = liveState.get(ws);
|
|
3059
3082
|
if (!state) return;
|
|
3060
|
-
|
|
3083
|
+
if (state.heartbeatInterval) {
|
|
3084
|
+
clearInterval(state.heartbeatInterval);
|
|
3085
|
+
}
|
|
3086
|
+
if (state.authTimeout) {
|
|
3087
|
+
clearTimeout(state.authTimeout);
|
|
3088
|
+
}
|
|
3061
3089
|
for (const downstream of state.downstreamSockets) {
|
|
3062
3090
|
try {
|
|
3063
3091
|
downstream.close();
|
|
@@ -3070,14 +3098,6 @@ export function createConsoleGatewayRoutes(
|
|
|
3070
3098
|
|
|
3071
3099
|
return {
|
|
3072
3100
|
onOpen(_event, ws) {
|
|
3073
|
-
if (!auth) {
|
|
3074
|
-
ws.send(
|
|
3075
|
-
JSON.stringify({ type: 'error', message: 'UNAUTHENTICATED' })
|
|
3076
|
-
);
|
|
3077
|
-
ws.close(4001, 'Unauthenticated');
|
|
3078
|
-
return;
|
|
3079
|
-
}
|
|
3080
|
-
|
|
3081
3101
|
if (selectedInstances.length === 0) {
|
|
3082
3102
|
ws.send(
|
|
3083
3103
|
JSON.stringify({
|
|
@@ -3090,17 +3110,215 @@ export function createConsoleGatewayRoutes(
|
|
|
3090
3110
|
return;
|
|
3091
3111
|
}
|
|
3092
3112
|
|
|
3093
|
-
const
|
|
3113
|
+
const state: {
|
|
3114
|
+
downstreamSockets: ConsoleGatewayDownstreamSocket[];
|
|
3115
|
+
heartbeatInterval: ReturnType<typeof setInterval> | null;
|
|
3116
|
+
authTimeout: ReturnType<typeof setTimeout> | null;
|
|
3117
|
+
isAuthenticated: boolean;
|
|
3118
|
+
} = {
|
|
3119
|
+
downstreamSockets: [],
|
|
3120
|
+
heartbeatInterval: null,
|
|
3121
|
+
authTimeout: null,
|
|
3122
|
+
isAuthenticated: false,
|
|
3123
|
+
};
|
|
3124
|
+
liveState.set(ws, state);
|
|
3125
|
+
|
|
3126
|
+
const startAuthenticatedSession = (
|
|
3127
|
+
upstreamBearerToken: string | null
|
|
3128
|
+
) => {
|
|
3129
|
+
if (state.isAuthenticated) {
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
state.isAuthenticated = true;
|
|
3133
|
+
if (state.authTimeout) {
|
|
3134
|
+
clearTimeout(state.authTimeout);
|
|
3135
|
+
state.authTimeout = null;
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
for (const instance of selectedInstances) {
|
|
3139
|
+
const downstreamQuery = new URLSearchParams();
|
|
3140
|
+
if (partitionId) {
|
|
3141
|
+
downstreamQuery.set('partitionId', partitionId);
|
|
3142
|
+
}
|
|
3143
|
+
if (replaySince) {
|
|
3144
|
+
downstreamQuery.set('since', replaySince);
|
|
3145
|
+
}
|
|
3146
|
+
downstreamQuery.set('replayLimit', String(replayLimit));
|
|
3147
|
+
|
|
3148
|
+
const downstreamUrl = buildConsoleEndpointUrl({
|
|
3149
|
+
instance,
|
|
3150
|
+
requestUrl: c.req.url,
|
|
3151
|
+
path: '/events/live',
|
|
3152
|
+
query: downstreamQuery,
|
|
3153
|
+
});
|
|
3154
|
+
|
|
3155
|
+
const downstreamSocket = createDownstreamSocket(downstreamUrl);
|
|
3156
|
+
const downstreamToken =
|
|
3157
|
+
instance.token?.trim() ?? upstreamBearerToken?.trim() ?? null;
|
|
3158
|
+
if (downstreamToken && downstreamSocket.send) {
|
|
3159
|
+
downstreamSocket.onopen = () => {
|
|
3160
|
+
try {
|
|
3161
|
+
downstreamSocket.send?.(
|
|
3162
|
+
JSON.stringify({
|
|
3163
|
+
type: 'auth',
|
|
3164
|
+
token: downstreamToken,
|
|
3165
|
+
})
|
|
3166
|
+
);
|
|
3167
|
+
} catch {
|
|
3168
|
+
// no-op
|
|
3169
|
+
}
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
downstreamSocket.onmessage = (message: MessageEvent) => {
|
|
3174
|
+
if (typeof message.data !== 'string') {
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
try {
|
|
3178
|
+
const payload = JSON.parse(message.data) as Record<
|
|
3179
|
+
string,
|
|
3180
|
+
unknown
|
|
3181
|
+
>;
|
|
3182
|
+
if (
|
|
3183
|
+
typeof payload.type === 'string' &&
|
|
3184
|
+
(payload.type === 'connected' ||
|
|
3185
|
+
payload.type === 'heartbeat')
|
|
3186
|
+
) {
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
const payloadData =
|
|
3191
|
+
payload.data &&
|
|
3192
|
+
typeof payload.data === 'object' &&
|
|
3193
|
+
!Array.isArray(payload.data)
|
|
3194
|
+
? { ...payload.data, instanceId: instance.instanceId }
|
|
3195
|
+
: { instanceId: instance.instanceId };
|
|
3196
|
+
|
|
3197
|
+
const event = {
|
|
3198
|
+
...payload,
|
|
3199
|
+
data: payloadData,
|
|
3200
|
+
instanceId: instance.instanceId,
|
|
3201
|
+
timestamp:
|
|
3202
|
+
typeof payload.timestamp === 'string'
|
|
3203
|
+
? payload.timestamp
|
|
3204
|
+
: new Date().toISOString(),
|
|
3205
|
+
};
|
|
3206
|
+
ws.send(JSON.stringify(event));
|
|
3207
|
+
} catch {
|
|
3208
|
+
// Ignore malformed downstream events
|
|
3209
|
+
}
|
|
3210
|
+
};
|
|
3211
|
+
|
|
3212
|
+
downstreamSocket.onerror = () => {
|
|
3213
|
+
try {
|
|
3214
|
+
ws.send(
|
|
3215
|
+
JSON.stringify({
|
|
3216
|
+
type: 'instance_error',
|
|
3217
|
+
instanceId: instance.instanceId,
|
|
3218
|
+
timestamp: new Date().toISOString(),
|
|
3219
|
+
})
|
|
3220
|
+
);
|
|
3221
|
+
} catch {
|
|
3222
|
+
// ignore send errors
|
|
3223
|
+
}
|
|
3224
|
+
};
|
|
3225
|
+
|
|
3226
|
+
state.downstreamSockets.push(downstreamSocket);
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
ws.send(
|
|
3230
|
+
JSON.stringify({
|
|
3231
|
+
type: 'connected',
|
|
3232
|
+
timestamp: new Date().toISOString(),
|
|
3233
|
+
instanceCount: selectedInstances.length,
|
|
3234
|
+
})
|
|
3235
|
+
);
|
|
3236
|
+
|
|
3237
|
+
const heartbeatInterval = setInterval(() => {
|
|
3238
|
+
try {
|
|
3239
|
+
ws.send(
|
|
3240
|
+
JSON.stringify({
|
|
3241
|
+
type: 'heartbeat',
|
|
3242
|
+
timestamp: new Date().toISOString(),
|
|
3243
|
+
})
|
|
3244
|
+
);
|
|
3245
|
+
} catch {
|
|
3246
|
+
clearInterval(heartbeatInterval);
|
|
3247
|
+
}
|
|
3248
|
+
}, heartbeatIntervalMs);
|
|
3249
|
+
state.heartbeatInterval = heartbeatInterval;
|
|
3250
|
+
};
|
|
3251
|
+
|
|
3252
|
+
if (initialAuth) {
|
|
3253
|
+
startAuthenticatedSession(
|
|
3254
|
+
parseBearerToken(c.req.header('Authorization'))
|
|
3255
|
+
);
|
|
3256
|
+
return;
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
state.authTimeout = setTimeout(() => {
|
|
3260
|
+
const current = liveState.get(ws);
|
|
3261
|
+
if (!current || current.isAuthenticated) {
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
closeUnauthenticated(ws);
|
|
3265
|
+
cleanup(ws);
|
|
3266
|
+
}, 5_000);
|
|
3267
|
+
},
|
|
3268
|
+
async onMessage(event, ws) {
|
|
3269
|
+
const state = liveState.get(ws);
|
|
3270
|
+
if (!state || state.isAuthenticated) {
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
if (typeof event.data !== 'string') {
|
|
3275
|
+
closeUnauthenticated(ws);
|
|
3276
|
+
cleanup(ws);
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
let token = '';
|
|
3281
|
+
try {
|
|
3282
|
+
const parsed = JSON.parse(event.data) as {
|
|
3283
|
+
type?: unknown;
|
|
3284
|
+
token?: unknown;
|
|
3285
|
+
};
|
|
3286
|
+
if (
|
|
3287
|
+
parsed.type === 'auth' &&
|
|
3288
|
+
typeof parsed.token === 'string' &&
|
|
3289
|
+
parsed.token.trim().length > 0
|
|
3290
|
+
) {
|
|
3291
|
+
token = parsed.token;
|
|
3292
|
+
}
|
|
3293
|
+
} catch {
|
|
3294
|
+
// Invalid auth message will be handled below.
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
if (!token) {
|
|
3298
|
+
closeUnauthenticated(ws);
|
|
3299
|
+
cleanup(ws);
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
const auth = await authenticateWithBearer(token);
|
|
3304
|
+
const current = liveState.get(ws);
|
|
3305
|
+
if (!current || current.isAuthenticated) {
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
3308
|
+
if (!auth) {
|
|
3309
|
+
closeUnauthenticated(ws);
|
|
3310
|
+
cleanup(ws);
|
|
3311
|
+
return;
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
current.isAuthenticated = true;
|
|
3315
|
+
if (current.authTimeout) {
|
|
3316
|
+
clearTimeout(current.authTimeout);
|
|
3317
|
+
current.authTimeout = null;
|
|
3318
|
+
}
|
|
3094
3319
|
|
|
3095
3320
|
for (const instance of selectedInstances) {
|
|
3096
3321
|
const downstreamQuery = new URLSearchParams();
|
|
3097
|
-
const downstreamToken = resolveForwardBearerToken({
|
|
3098
|
-
c,
|
|
3099
|
-
instance,
|
|
3100
|
-
});
|
|
3101
|
-
if (downstreamToken) {
|
|
3102
|
-
downstreamQuery.set('token', downstreamToken);
|
|
3103
|
-
}
|
|
3104
3322
|
if (partitionId) {
|
|
3105
3323
|
downstreamQuery.set('partitionId', partitionId);
|
|
3106
3324
|
}
|
|
@@ -3117,6 +3335,24 @@ export function createConsoleGatewayRoutes(
|
|
|
3117
3335
|
});
|
|
3118
3336
|
|
|
3119
3337
|
const downstreamSocket = createDownstreamSocket(downstreamUrl);
|
|
3338
|
+
const upstreamToken = token.trim();
|
|
3339
|
+
const downstreamToken =
|
|
3340
|
+
instance.token?.trim() ||
|
|
3341
|
+
(upstreamToken.length > 0 ? upstreamToken : null);
|
|
3342
|
+
if (downstreamToken && downstreamSocket.send) {
|
|
3343
|
+
downstreamSocket.onopen = () => {
|
|
3344
|
+
try {
|
|
3345
|
+
downstreamSocket.send?.(
|
|
3346
|
+
JSON.stringify({
|
|
3347
|
+
type: 'auth',
|
|
3348
|
+
token: downstreamToken,
|
|
3349
|
+
})
|
|
3350
|
+
);
|
|
3351
|
+
} catch {
|
|
3352
|
+
// no-op
|
|
3353
|
+
}
|
|
3354
|
+
};
|
|
3355
|
+
}
|
|
3120
3356
|
|
|
3121
3357
|
downstreamSocket.onmessage = (message: MessageEvent) => {
|
|
3122
3358
|
if (typeof message.data !== 'string') {
|
|
@@ -3142,7 +3378,7 @@ export function createConsoleGatewayRoutes(
|
|
|
3142
3378
|
? { ...payload.data, instanceId: instance.instanceId }
|
|
3143
3379
|
: { instanceId: instance.instanceId };
|
|
3144
3380
|
|
|
3145
|
-
const
|
|
3381
|
+
const liveEvent = {
|
|
3146
3382
|
...payload,
|
|
3147
3383
|
data: payloadData,
|
|
3148
3384
|
instanceId: instance.instanceId,
|
|
@@ -3151,7 +3387,7 @@ export function createConsoleGatewayRoutes(
|
|
|
3151
3387
|
? payload.timestamp
|
|
3152
3388
|
: new Date().toISOString(),
|
|
3153
3389
|
};
|
|
3154
|
-
ws.send(JSON.stringify(
|
|
3390
|
+
ws.send(JSON.stringify(liveEvent));
|
|
3155
3391
|
} catch {
|
|
3156
3392
|
// Ignore malformed downstream events
|
|
3157
3393
|
}
|
|
@@ -3171,7 +3407,7 @@ export function createConsoleGatewayRoutes(
|
|
|
3171
3407
|
}
|
|
3172
3408
|
};
|
|
3173
3409
|
|
|
3174
|
-
downstreamSockets.push(downstreamSocket);
|
|
3410
|
+
current.downstreamSockets.push(downstreamSocket);
|
|
3175
3411
|
}
|
|
3176
3412
|
|
|
3177
3413
|
ws.send(
|
|
@@ -3194,11 +3430,7 @@ export function createConsoleGatewayRoutes(
|
|
|
3194
3430
|
clearInterval(heartbeatInterval);
|
|
3195
3431
|
}
|
|
3196
3432
|
}, heartbeatIntervalMs);
|
|
3197
|
-
|
|
3198
|
-
liveState.set(ws, {
|
|
3199
|
-
downstreamSockets,
|
|
3200
|
-
heartbeatInterval,
|
|
3201
|
-
});
|
|
3433
|
+
current.heartbeatInterval = heartbeatInterval;
|
|
3202
3434
|
},
|
|
3203
3435
|
onClose(_event, ws) {
|
|
3204
3436
|
cleanup(ws);
|