@holo-js/broadcast 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  resolveBroadcastChannelGuard,
7
7
  resolveBroadcastWhisperSchema,
8
8
  validateBroadcastWhisperPayload
9
- } from "./chunk-U5JDBKXC.mjs";
9
+ } from "./chunk-JVDFH2TW.mjs";
10
10
  import {
11
11
  renderBroadcastClientConfigResponse,
12
12
  resolveBroadcastClientConfig
@@ -21,10 +21,17 @@ import {
21
21
  getBroadcastRuntimeBindings,
22
22
  getRegisteredBroadcastDriver,
23
23
  listRegisteredBroadcastDrivers,
24
+ loadBroadcastPluginDrivers,
24
25
  registerBroadcastDriver,
25
26
  resetBroadcastDriverRegistry,
27
+ resetBroadcastPluginDrivers,
26
28
  resetBroadcastRuntime
27
- } from "./chunk-TTKGDABI.mjs";
29
+ } from "./chunk-6DCCSFFM.mjs";
30
+ import {
31
+ defineBroadcastConfig,
32
+ holoBroadcastDefaults,
33
+ normalizeBroadcastConfig
34
+ } from "./chunk-XLUGF257.mjs";
28
35
  import {
29
36
  broadcastInternals,
30
37
  channel,
@@ -40,23 +47,22 @@ import {
40
47
  } from "./chunk-HE6HN7ID.mjs";
41
48
 
42
49
  // src/worker.ts
43
- import { createHash, createHmac, randomInt, randomUUID, timingSafeEqual } from "crypto";
50
+ import { createHash, createHmac as createHmac2, randomInt, randomUUID, timingSafeEqual as timingSafeEqual2 } from "crypto";
44
51
  import { createServer } from "http";
45
- var MAX_PUBLISH_TIMESTAMP_SKEW_SECONDS = 300;
46
- function normalizeRequiredString(value, label) {
52
+
53
+ // src/workerProtocol.ts
54
+ import { createHmac, timingSafeEqual } from "crypto";
55
+ function normalizeWorkerRequiredString(value, label) {
47
56
  const normalized = value.trim();
48
57
  if (!normalized) {
49
58
  throw new Error(`[@holo-js/broadcast] ${label} must be a non-empty string.`);
50
59
  }
51
60
  return normalized;
52
61
  }
53
- function escapeRegExp(value) {
54
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
55
- }
56
- function parseSocketMessage(rawMessage) {
62
+ function parseWorkerSocketMessage(rawMessage) {
57
63
  const message = parseJsonObject(rawMessage, "Websocket message");
58
- const event = normalizeRequiredString(String(message.event ?? ""), "Websocket event");
59
- const channel2 = typeof message.channel === "string" ? normalizeRequiredString(message.channel, "Websocket channel") : void 0;
64
+ const event = normalizeWorkerRequiredString(String(message.event ?? ""), "Websocket event");
65
+ const channel2 = typeof message.channel === "string" ? normalizeWorkerRequiredString(message.channel, "Websocket channel") : void 0;
60
66
  const data = typeof message.data === "string" ? parseJsonObject(message.data, "Websocket message data") : isPlainObject(message.data) ? message.data : {};
61
67
  return Object.freeze({
62
68
  event,
@@ -64,41 +70,12 @@ function parseSocketMessage(rawMessage) {
64
70
  data
65
71
  });
66
72
  }
67
- function normalizeRealtimeAction(value) {
68
- if (value === "query" || value === "mutation" || value === "subscribe" || value === "unsubscribe") {
69
- return value;
70
- }
71
- throw new Error("[@holo-js/broadcast] Realtime action is invalid.");
72
- }
73
- function normalizeRealtimeArgs(value) {
74
- if (!isPlainObject(value)) {
75
- return {};
76
- }
77
- return value;
78
- }
79
- function isRecord(value) {
80
- return isObjectRecord(value);
81
- }
82
- function parseRealtimeSocketMessage(data) {
83
- const id = normalizeRequiredString(String(data.id ?? ""), "Realtime request id");
84
- const action = normalizeRealtimeAction(data.action);
85
- const name = typeof data.name === "string" ? normalizeRequiredString(data.name, "Realtime definition name") : void 0;
86
- if (action !== "unsubscribe" && !name) {
87
- throw new Error("[@holo-js/broadcast] Realtime definition name is required.");
88
- }
89
- return Object.freeze({
90
- id,
91
- action,
92
- ...typeof name === "undefined" ? {} : { name },
93
- args: normalizeRealtimeArgs(data.args)
94
- });
95
- }
96
- function normalizePublishBody(value) {
73
+ function normalizeWorkerPublishBody(value) {
97
74
  if (!value || typeof value !== "object" || Array.isArray(value)) {
98
75
  throw new Error("[@holo-js/broadcast] Publish payload must be a JSON object.");
99
76
  }
100
77
  const body = value;
101
- const name = typeof body.name === "string" ? normalizeRequiredString(body.name, "Publish name") : typeof body.event === "string" ? normalizeRequiredString(body.event, "Publish event") : "";
78
+ const name = typeof body.name === "string" ? normalizeWorkerRequiredString(body.name, "Publish name") : typeof body.event === "string" ? normalizeWorkerRequiredString(body.event, "Publish event") : "";
102
79
  if (!name) {
103
80
  throw new Error("[@holo-js/broadcast] Publish payload must include an event name.");
104
81
  }
@@ -106,13 +83,13 @@ function normalizePublishBody(value) {
106
83
  if (typeof channel2 !== "string") {
107
84
  throw new Error("[@holo-js/broadcast] Publish channel must be a non-empty string.");
108
85
  }
109
- return normalizeRequiredString(channel2, "Publish channel");
110
- }) : typeof body.channel === "string" ? [normalizeRequiredString(body.channel, "Publish channel")] : [];
86
+ return normalizeWorkerRequiredString(channel2, "Publish channel");
87
+ }) : typeof body.channel === "string" ? [normalizeWorkerRequiredString(body.channel, "Publish channel")] : [];
111
88
  if (channels.length === 0) {
112
89
  throw new Error("[@holo-js/broadcast] Publish payload must include at least one channel.");
113
90
  }
114
91
  const data = typeof body.data === "string" ? body.data : JSON.stringify(body.data ?? {});
115
- const socketId = typeof body.socket_id === "string" ? normalizeRequiredString(body.socket_id, "Publish socket_id") : void 0;
92
+ const socketId = typeof body.socket_id === "string" ? normalizeWorkerRequiredString(body.socket_id, "Publish socket_id") : void 0;
116
93
  return Object.freeze({
117
94
  name,
118
95
  channels: Object.freeze(channels),
@@ -120,19 +97,178 @@ function normalizePublishBody(value) {
120
97
  ...typeof socketId === "undefined" ? {} : { socket_id: socketId }
121
98
  });
122
99
  }
123
- function createPusherSignature(secret, method, pathname, params) {
100
+ function createWorkerPusherSignature(secret, method, pathname, params) {
124
101
  const sorted = [...params.entries()].filter(([key]) => key !== "auth_signature").sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
125
102
  const payload = `${method.toUpperCase()}
126
103
  ${pathname}
127
104
  ${sorted}`;
128
105
  return createHmac("sha256", secret).update(payload).digest("hex");
129
106
  }
130
- function verifyPusherSignature(providedSignature, expectedSignature) {
107
+ function verifyWorkerPusherSignature(providedSignature, expectedSignature) {
131
108
  if (providedSignature.length !== expectedSignature.length || !/^[a-f0-9]+$/i.test(providedSignature) || !/^[a-f0-9]+$/i.test(expectedSignature)) {
132
109
  return false;
133
110
  }
134
111
  return timingSafeEqual(Buffer.from(providedSignature, "hex"), Buffer.from(expectedSignature, "hex"));
135
112
  }
113
+ function parseWorkerChannelKind(channel2) {
114
+ if (channel2.startsWith("private-")) {
115
+ return Object.freeze({
116
+ kind: "private",
117
+ canonical: channel2.slice("private-".length)
118
+ });
119
+ }
120
+ if (channel2.startsWith("presence-")) {
121
+ return Object.freeze({
122
+ kind: "presence",
123
+ canonical: channel2.slice("presence-".length)
124
+ });
125
+ }
126
+ return Object.freeze({
127
+ kind: "public",
128
+ canonical: channel2
129
+ });
130
+ }
131
+
132
+ // src/workerNodeTransport.ts
133
+ var BroadcastPayloadTooLargeError = class extends Error {
134
+ };
135
+ async function readLimitedRequestText(request, maxBytes) {
136
+ const contentLength = Number(request.headers.get("content-length"));
137
+ if (Number.isFinite(contentLength) && contentLength > maxBytes) {
138
+ throw new BroadcastPayloadTooLargeError();
139
+ }
140
+ if (!request.body) return "";
141
+ const reader = request.body.getReader();
142
+ const chunks = [];
143
+ let size = 0;
144
+ while (true) {
145
+ const result = await reader.read();
146
+ if (result.done) break;
147
+ size += result.value.byteLength;
148
+ if (size > maxBytes) {
149
+ await reader.cancel();
150
+ throw new BroadcastPayloadTooLargeError();
151
+ }
152
+ chunks.push(result.value);
153
+ }
154
+ const bytes = new Uint8Array(size);
155
+ let offset = 0;
156
+ for (const chunk of chunks) {
157
+ bytes.set(chunk, offset);
158
+ offset += chunk.byteLength;
159
+ }
160
+ return new TextDecoder().decode(bytes);
161
+ }
162
+ function toNodeHeaders(headers) {
163
+ const normalized = new Headers();
164
+ for (const [key, value] of Object.entries(headers)) {
165
+ if (typeof value === "undefined") continue;
166
+ if (Array.isArray(value)) {
167
+ for (const item of value) normalized.append(key, item);
168
+ } else {
169
+ normalized.set(key, value);
170
+ }
171
+ }
172
+ return normalized;
173
+ }
174
+ function toNodeRequestUrl(request, fallbackHost) {
175
+ const path = request.url ?? "/";
176
+ const host = request.headers.host ?? fallbackHost;
177
+ return `http://${host}${path}`;
178
+ }
179
+ async function readNodeRequestBody(request, maxBytes) {
180
+ if (request.method === "GET" || request.method === "HEAD") return void 0;
181
+ const chunks = [];
182
+ let size = 0;
183
+ for await (const chunk of request) {
184
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
185
+ size += buffer.byteLength;
186
+ if (size > maxBytes) throw new BroadcastPayloadTooLargeError();
187
+ chunks.push(buffer);
188
+ }
189
+ return chunks.length === 0 ? void 0 : Buffer.concat(chunks);
190
+ }
191
+ function writeNodeRequestBodyError(response, error) {
192
+ if (error instanceof BroadcastPayloadTooLargeError) {
193
+ response.statusCode = 413;
194
+ response.end("Payload Too Large");
195
+ return;
196
+ }
197
+ response.statusCode = 500;
198
+ response.end("Internal Server Error");
199
+ }
200
+ async function writeNodeResponse(response, value) {
201
+ response.statusCode = value.status;
202
+ response.statusMessage = value.statusText;
203
+ value.headers.forEach((headerValue, headerName) => response.setHeader(headerName, headerValue));
204
+ response.end(Buffer.from(await value.arrayBuffer()));
205
+ }
206
+ function decodeNodeWebSocketMessage(message) {
207
+ if (typeof message === "string") return message;
208
+ if (message instanceof ArrayBuffer) return new TextDecoder().decode(new Uint8Array(message));
209
+ if (Array.isArray(message)) return Buffer.concat(message).toString("utf8");
210
+ if (message instanceof Uint8Array) return Buffer.from(message).toString("utf8");
211
+ return String(message);
212
+ }
213
+ function getNodeWebSocketMessageBytes(message) {
214
+ if (typeof message === "string") return Buffer.byteLength(message);
215
+ if (message instanceof ArrayBuffer || message instanceof Uint8Array) return message.byteLength;
216
+ return message.reduce((size, chunk) => size + chunk.byteLength, 0);
217
+ }
218
+
219
+ // src/worker.ts
220
+ function createScalingMessageListener(runtime) {
221
+ return (payload) => {
222
+ void runtime.receiveScalingMessage(payload).catch((error) => {
223
+ logScalingMessageError(error);
224
+ });
225
+ };
226
+ }
227
+ function isAllowedWorkerOrigin(request, allowedOrigins) {
228
+ const origin = request.headers.get("origin");
229
+ if (!origin) {
230
+ return true;
231
+ }
232
+ try {
233
+ const normalized = new URL(origin).origin;
234
+ return allowedOrigins.includes("*") || allowedOrigins.includes(normalized);
235
+ } catch {
236
+ return false;
237
+ }
238
+ }
239
+ var MAX_PUBLISH_TIMESTAMP_SKEW_SECONDS = 300;
240
+ function escapeRegExp(value) {
241
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
242
+ }
243
+ function normalizeRealtimeAction(value) {
244
+ if (value === "query" || value === "mutation" || value === "subscribe" || value === "unsubscribe") {
245
+ return value;
246
+ }
247
+ throw new Error("[@holo-js/broadcast] Realtime action is invalid.");
248
+ }
249
+ function normalizeRealtimeArgs(value) {
250
+ if (!isPlainObject(value)) {
251
+ return {};
252
+ }
253
+ return value;
254
+ }
255
+ function isRecord(value) {
256
+ return isObjectRecord(value);
257
+ }
258
+ function parseRealtimeSocketMessage(data) {
259
+ const id = normalizeWorkerRequiredString(String(data.id ?? ""), "Realtime request id");
260
+ const action = normalizeRealtimeAction(data.action);
261
+ const name = typeof data.name === "string" ? normalizeWorkerRequiredString(data.name, "Realtime definition name") : void 0;
262
+ if (action !== "unsubscribe" && !name) {
263
+ throw new Error("[@holo-js/broadcast] Realtime definition name is required.");
264
+ }
265
+ return Object.freeze({
266
+ id,
267
+ action,
268
+ ...typeof name === "undefined" ? {} : { name },
269
+ args: normalizeRealtimeArgs(data.args)
270
+ });
271
+ }
136
272
  function logSocketMessageError(socketId, error) {
137
273
  const message = error instanceof Error ? error.message : String(error);
138
274
  console.error(`[@holo-js/broadcast] WebSocket message handling failed for socket "${socketId}": ${message}`);
@@ -152,18 +288,18 @@ function logRealtimeSubscriptionCleanupError(socketId, subscriptionId, error) {
152
288
  function safeEqual(left, right) {
153
289
  const leftBuffer = Buffer.from(left);
154
290
  const rightBuffer = Buffer.from(right);
155
- return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
291
+ return leftBuffer.length === rightBuffer.length && timingSafeEqual2(leftBuffer, rightBuffer);
156
292
  }
157
293
  function signChannelAuth(secret, socketId, channel2, channelData) {
158
294
  const value = channelData ? `${socketId}:${channel2}:${channelData}` : `${socketId}:${channel2}`;
159
- return createHmac("sha256", secret).update(value).digest("hex");
295
+ return createHmac2("sha256", secret).update(value).digest("hex");
160
296
  }
161
297
  function parseClientChannelAuth(data) {
162
298
  if (typeof data.auth !== "string") {
163
299
  return void 0;
164
300
  }
165
301
  return Object.freeze({
166
- auth: normalizeRequiredString(data.auth, "Subscription auth"),
302
+ auth: normalizeWorkerRequiredString(data.auth, "Subscription auth"),
167
303
  ...typeof data.channel_data === "string" ? { channelData: data.channel_data } : {}
168
304
  });
169
305
  }
@@ -174,7 +310,7 @@ function parseSignedChannelData(value) {
174
310
  });
175
311
  }
176
312
  const data = parseJsonObject(value, "Subscription channel data");
177
- const whispers = Array.isArray(data.whispers) ? Object.freeze(data.whispers.map((item) => normalizeRequiredString(String(item), "Auth whisper"))) : Object.freeze([]);
313
+ const whispers = Array.isArray(data.whispers) ? Object.freeze(data.whispers.map((item) => normalizeWorkerRequiredString(String(item), "Auth whisper"))) : Object.freeze([]);
178
314
  const member = data.member && typeof data.member === "object" && !Array.isArray(data.member) ? Object.freeze(data.member) : void 0;
179
315
  return Object.freeze({
180
316
  whispers,
@@ -199,29 +335,14 @@ function unsubscribeRealtimeSubscription(socket, id, subscription) {
199
335
  logRealtimeSubscriptionCleanupError(socket.socketId, id, error);
200
336
  }
201
337
  }
202
- function parseChannelKind(channel2) {
203
- if (channel2.startsWith("private-")) {
204
- return Object.freeze({
205
- kind: "private",
206
- canonical: channel2.slice("private-".length)
207
- });
208
- }
209
- if (channel2.startsWith("presence-")) {
210
- return Object.freeze({
211
- kind: "presence",
212
- canonical: channel2.slice("presence-".length)
213
- });
214
- }
215
- return Object.freeze({
216
- kind: "public",
217
- canonical: channel2
218
- });
338
+ function isRealtimeSubscriptionCurrent(socket, id, token) {
339
+ return socket.active && socket.realtimeSubscriptionTokens.get(id) === token;
219
340
  }
220
341
  function createSocketId() {
221
342
  return `${randomInt(1, 999999)}.${randomInt(1, 999999)}`;
222
343
  }
223
344
  function createScalingNodeId() {
224
- return normalizeRequiredString(`${process.env.HOSTNAME ?? "node"}-${randomUUID()}`, "Broadcast worker node id");
345
+ return normalizeWorkerRequiredString(`${process.env.HOSTNAME ?? "node"}-${randomUUID()}`, "Broadcast worker node id");
225
346
  }
226
347
  function resolveScalingEventChannel(connection) {
227
348
  return `holo:broadcast:scaling:${connection}:events`;
@@ -268,14 +389,15 @@ function parsePresenceHashMembers(values) {
268
389
  function resolveRedisScalingConnection(queueConfig, connectionName, redisConfig) {
269
390
  const queueConnection = queueConfig?.connections[connectionName];
270
391
  if (queueConnection?.driver === "redis") {
392
+ const redisQueueConnection = queueConnection;
271
393
  return Object.freeze({
272
- ...typeof queueConnection.redis.url === "undefined" ? {} : { url: queueConnection.redis.url },
273
- ...typeof queueConnection.redis.clusters === "undefined" ? {} : { clusters: queueConnection.redis.clusters },
274
- host: queueConnection.redis.host,
275
- port: queueConnection.redis.port,
276
- username: queueConnection.redis.username,
277
- password: queueConnection.redis.password,
278
- db: queueConnection.redis.db
394
+ ...typeof redisQueueConnection.redis.url === "undefined" ? {} : { url: redisQueueConnection.redis.url },
395
+ ...typeof redisQueueConnection.redis.clusters === "undefined" ? {} : { clusters: redisQueueConnection.redis.clusters },
396
+ host: redisQueueConnection.redis.host,
397
+ port: redisQueueConnection.redis.port,
398
+ username: redisQueueConnection.redis.username,
399
+ password: redisQueueConnection.redis.password,
400
+ db: redisQueueConnection.redis.db
279
401
  });
280
402
  }
281
403
  const redisConnection = redisConfig?.connections[connectionName];
@@ -478,7 +600,7 @@ function buildWorkerApps(config) {
478
600
  continue;
479
601
  }
480
602
  const holoConnection = connection;
481
- const authEndpoint = typeof holoConnection.clientOptions.authEndpoint === "string" ? normalizeRequiredString(holoConnection.clientOptions.authEndpoint, `Broadcast connection "${name}" authEndpoint`) : void 0;
603
+ const authEndpoint = typeof holoConnection.clientOptions.authEndpoint === "string" ? normalizeWorkerRequiredString(holoConnection.clientOptions.authEndpoint, `Broadcast connection "${name}" authEndpoint`) : void 0;
482
604
  if (appsByKey.has(holoConnection.key)) {
483
605
  throw new Error(`[@holo-js/broadcast] duplicate broadcast app key "${holoConnection.key}" is already configured.`);
484
606
  }
@@ -503,7 +625,7 @@ function pusherEvent(event, data, channel2) {
503
625
  });
504
626
  }
505
627
  async function authenticateSubscription(app, connection, channel2, clientAuth, channelAuth, fetcher) {
506
- const { kind, canonical } = parseChannelKind(channel2);
628
+ const { kind, canonical } = parseWorkerChannelKind(channel2);
507
629
  if (kind === "public") {
508
630
  return Object.freeze({
509
631
  whispers: Object.freeze([])
@@ -531,7 +653,7 @@ async function authenticateSubscription(app, connection, channel2, clientAuth, c
531
653
  throw new Error(`[@holo-js/broadcast] Channel authorization rejected (${response.status}).`);
532
654
  }
533
655
  const body = await response.json();
534
- const whispers = Array.isArray(body.whispers) ? Object.freeze(body.whispers.map((value) => normalizeRequiredString(String(value), "Auth whisper"))) : Object.freeze([]);
656
+ const whispers = Array.isArray(body.whispers) ? Object.freeze(body.whispers.map((value) => normalizeWorkerRequiredString(String(value), "Auth whisper"))) : Object.freeze([]);
535
657
  const member = body.member && typeof body.member === "object" && !Array.isArray(body.member) ? Object.freeze(body.member) : void 0;
536
658
  return Object.freeze({
537
659
  whispers,
@@ -564,6 +686,42 @@ async function authenticateSubscription(app, connection, channel2, clientAuth, c
564
686
  ...authorized.type === "presence" ? { member: authorized.member } : {}
565
687
  });
566
688
  }
689
+ function resolveRealtimeErrorStatus(error) {
690
+ if (!isRecord(error)) {
691
+ return void 0;
692
+ }
693
+ const decision = isRecord(error.decision) ? error.decision : void 0;
694
+ const status = decision?.status ?? error.status ?? error.statusCode;
695
+ if (typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599) {
696
+ return status;
697
+ }
698
+ const name = typeof error.name === "string" ? error.name : "";
699
+ if (name === "RealtimeUnauthorizedError") {
700
+ return 401;
701
+ }
702
+ if (name === "RealtimeForbiddenError") {
703
+ return 403;
704
+ }
705
+ return void 0;
706
+ }
707
+ function resolveRealtimeErrorCode(error) {
708
+ if (!isRecord(error)) {
709
+ return void 0;
710
+ }
711
+ const decision = isRecord(error.decision) ? error.decision : void 0;
712
+ const code = decision?.code ?? error.code;
713
+ return typeof code === "string" ? code : void 0;
714
+ }
715
+ function resolveRealtimeErrorKind(error, status) {
716
+ if (!isRecord(error)) {
717
+ return "runtime";
718
+ }
719
+ const name = typeof error.name === "string" ? error.name : "";
720
+ if (name === "AuthorizationError" || name === "RealtimeUnauthorizedError" || name === "RealtimeForbiddenError" || status === 401 || status === 403 || status === 404) {
721
+ return "authorization";
722
+ }
723
+ return name === "RealtimeAuthUnavailableError" ? "transport" : "runtime";
724
+ }
567
725
  function createBroadcastWorkerRuntime(options) {
568
726
  const appsByKey = buildWorkerApps(options.config);
569
727
  const connectedSockets = /* @__PURE__ */ new Map();
@@ -573,14 +731,12 @@ function createBroadcastWorkerRuntime(options) {
573
731
  const presenceSockets = /* @__PURE__ */ new Map();
574
732
  const scaling = options.scaling;
575
733
  const startedAt = options.now?.() ?? Date.now();
576
- const scalingUnsubscribe = options.scalingUnsubscribe ? Promise.resolve(options.scalingUnsubscribe) : scaling && options.scalingAutoSubscribe !== false ? scaling.adapter.subscribe(scaling.eventChannel, (payload) => {
577
- void handleScalingMessage(payload).catch((error) => {
578
- logScalingMessageError(error);
579
- });
580
- }) : Promise.resolve(async () => {
734
+ const scalingUnsubscribe = options.scalingUnsubscribe ? Promise.resolve(options.scalingUnsubscribe) : scaling && options.scalingAutoSubscribe !== false ? scaling.adapter.subscribe(scaling.eventChannel, createScalingMessageListener({
735
+ receiveScalingMessage: handleScalingMessage
736
+ })) : Promise.resolve(async () => {
581
737
  });
582
738
  function createPresenceSocketRef(channel2, socketId) {
583
- return scaling && parseChannelKind(channel2).kind === "presence" ? createNodeSocketRef(scaling.nodeId, socketId) : socketId;
739
+ return scaling && parseWorkerChannelKind(channel2).kind === "presence" ? createNodeSocketRef(scaling.nodeId, socketId) : socketId;
584
740
  }
585
741
  function setPresenceState(key, socketMembers) {
586
742
  if (socketMembers.size === 0) {
@@ -614,7 +770,7 @@ function createBroadcastWorkerRuntime(options) {
614
770
  if (!scaling) {
615
771
  return;
616
772
  }
617
- const { kind } = parseChannelKind(channel2);
773
+ const { kind } = parseWorkerChannelKind(channel2);
618
774
  if (kind !== "presence") {
619
775
  return;
620
776
  }
@@ -743,43 +899,10 @@ function createBroadcastWorkerRuntime(options) {
743
899
  );
744
900
  }
745
901
  function sendRealtimeMessage(socket, event, data) {
746
- socket.send(pusherEvent(event, data));
747
- }
748
- function resolveRealtimeErrorStatus(error) {
749
- if (!isRecord(error)) {
750
- return void 0;
751
- }
752
- const decision = isRecord(error.decision) ? error.decision : void 0;
753
- const status = decision?.status ?? error.status ?? error.statusCode;
754
- if (typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599) {
755
- return status;
756
- }
757
- const name = typeof error.name === "string" ? error.name : "";
758
- if (name === "RealtimeUnauthorizedError") {
759
- return 401;
760
- }
761
- if (name === "RealtimeForbiddenError") {
762
- return 403;
763
- }
764
- return void 0;
765
- }
766
- function resolveRealtimeErrorCode(error) {
767
- if (!isRecord(error)) {
768
- return void 0;
769
- }
770
- const decision = isRecord(error.decision) ? error.decision : void 0;
771
- const code = decision?.code ?? error.code;
772
- return typeof code === "string" ? code : void 0;
773
- }
774
- function resolveRealtimeErrorKind(error, status) {
775
- if (!isRecord(error)) {
776
- return "runtime";
777
- }
778
- const name = typeof error.name === "string" ? error.name : "";
779
- if (name === "AuthorizationError" || name === "RealtimeUnauthorizedError" || name === "RealtimeForbiddenError" || status === 401 || status === 403 || status === 404) {
780
- return "authorization";
902
+ if (!socket.active) {
903
+ return;
781
904
  }
782
- return name === "RealtimeAuthUnavailableError" ? "transport" : "runtime";
905
+ socket.send(pusherEvent(event, data));
783
906
  }
784
907
  function sendRealtimeError(socket, id, error) {
785
908
  const status = resolveRealtimeErrorStatus(error);
@@ -812,6 +935,7 @@ function createBroadcastWorkerRuntime(options) {
812
935
  }
813
936
  if (request.action === "unsubscribe") {
814
937
  const subscription = socket.realtimeSubscriptions.get(request.id);
938
+ socket.realtimeSubscriptionTokens.delete(request.id);
815
939
  if (subscription) {
816
940
  unsubscribeRealtimeSubscription(socket, request.id, subscription);
817
941
  }
@@ -822,6 +946,7 @@ function createBroadcastWorkerRuntime(options) {
822
946
  return;
823
947
  }
824
948
  const context = createRealtimeExecutionContext(socket);
949
+ let pendingSubscriptionToken;
825
950
  try {
826
951
  if (request.action === "query") {
827
952
  const result = await realtime.query(request.name, request.args, context);
@@ -845,24 +970,53 @@ function createBroadcastWorkerRuntime(options) {
845
970
  return;
846
971
  }
847
972
  const previousSubscription = socket.realtimeSubscriptions.get(request.id);
973
+ socket.realtimeSubscriptionTokens.delete(request.id);
848
974
  if (previousSubscription) {
849
975
  unsubscribeRealtimeSubscription(socket, request.id, previousSubscription);
850
976
  socket.realtimeSubscriptions.delete(request.id);
851
977
  }
978
+ const subscriptionToken = {};
979
+ pendingSubscriptionToken = subscriptionToken;
980
+ socket.realtimeSubscriptionTokens.set(request.id, subscriptionToken);
852
981
  const subscription = await realtime.subscribe(request.name, request.args, {
853
982
  context,
854
983
  onData(snapshot) {
984
+ if (!isRealtimeSubscriptionCurrent(socket, request.id, subscriptionToken)) {
985
+ return;
986
+ }
855
987
  sendRealtimeMessage(socket, "holo:realtime:snapshot", {
856
988
  id: request.id,
857
989
  snapshot
858
990
  });
859
991
  },
992
+ onPatch(patch) {
993
+ if (!isRealtimeSubscriptionCurrent(socket, request.id, subscriptionToken)) {
994
+ return;
995
+ }
996
+ sendRealtimeMessage(socket, "holo:realtime:patch", {
997
+ id: request.id,
998
+ patch
999
+ });
1000
+ },
860
1001
  onError(error) {
1002
+ if (!isRealtimeSubscriptionCurrent(socket, request.id, subscriptionToken)) {
1003
+ return;
1004
+ }
861
1005
  sendRealtimeError(socket, request.id, error);
862
1006
  }
863
1007
  });
1008
+ if (!isRealtimeSubscriptionCurrent(socket, request.id, subscriptionToken)) {
1009
+ unsubscribeRealtimeSubscription(socket, request.id, subscription);
1010
+ return;
1011
+ }
864
1012
  socket.realtimeSubscriptions.set(request.id, subscription);
865
1013
  } catch (error) {
1014
+ if (pendingSubscriptionToken) {
1015
+ if (!isRealtimeSubscriptionCurrent(socket, request.id, pendingSubscriptionToken)) {
1016
+ return;
1017
+ }
1018
+ socket.realtimeSubscriptionTokens.delete(request.id);
1019
+ }
866
1020
  sendRealtimeError(socket, request.id, error);
867
1021
  }
868
1022
  }
@@ -978,7 +1132,7 @@ function createBroadcastWorkerRuntime(options) {
978
1132
  }
979
1133
  }
980
1134
  async function handleSubscribe(socket, rawChannel, clientAuth) {
981
- const channel2 = normalizeRequiredString(rawChannel, "Subscription channel");
1135
+ const channel2 = normalizeWorkerRequiredString(rawChannel, "Subscription channel");
982
1136
  const authorization = await authenticateSubscription(socket.app, socket, channel2, clientAuth, options.channelAuth, options.fetch);
983
1137
  if (!socket.active || connectedSockets.get(socket.socketId) !== socket) {
984
1138
  return;
@@ -997,7 +1151,7 @@ function createBroadcastWorkerRuntime(options) {
997
1151
  channelWhispers.delete(key);
998
1152
  }
999
1153
  }
1000
- const { kind } = parseChannelKind(channel2);
1154
+ const { kind } = parseWorkerChannelKind(channel2);
1001
1155
  if (kind === "presence") {
1002
1156
  const member = authorization.member ?? Object.freeze({ id: socket.socketId });
1003
1157
  const synchronized = await synchronizePresenceChannel(
@@ -1019,7 +1173,7 @@ function createBroadcastWorkerRuntime(options) {
1019
1173
  socket.send(pusherEvent("pusher_internal:subscription_succeeded", {}, channel2));
1020
1174
  }
1021
1175
  async function handleUnsubscribe(socket, rawChannel) {
1022
- const channel2 = normalizeRequiredString(rawChannel, "Unsubscribe channel");
1176
+ const channel2 = normalizeWorkerRequiredString(rawChannel, "Unsubscribe channel");
1023
1177
  socket.subscribedChannels.delete(channel2);
1024
1178
  const removedPresenceMember = removeSubscriptionLocal(socket.app.appId, socket.socketId, channel2);
1025
1179
  if (removedPresenceMember.removed && removedPresenceMember.member) {
@@ -1032,11 +1186,11 @@ function createBroadcastWorkerRuntime(options) {
1032
1186
  socket.send(pusherEvent("pusher_internal:unsubscribed", {}, channel2));
1033
1187
  }
1034
1188
  async function handleClientEvent(socket, message) {
1035
- const channel2 = normalizeRequiredString(message.channel ?? "", "Whisper channel");
1189
+ const channel2 = normalizeWorkerRequiredString(message.channel ?? "", "Whisper channel");
1036
1190
  if (!socket.subscribedChannels.has(channel2)) {
1037
1191
  throw new Error(`[@holo-js/broadcast] Socket is not subscribed to "${channel2}".`);
1038
1192
  }
1039
- const { kind, canonical } = parseChannelKind(channel2);
1193
+ const { kind, canonical } = parseWorkerChannelKind(channel2);
1040
1194
  if (kind === "public") {
1041
1195
  throw new Error("[@holo-js/broadcast] Client events are only allowed on private or presence channels.");
1042
1196
  }
@@ -1055,15 +1209,9 @@ function createBroadcastWorkerRuntime(options) {
1055
1209
  appId: socket.app.appId,
1056
1210
  socket_id: socket.socketId
1057
1211
  });
1058
- await publishToChannels(payload, {
1059
- fromScaling: false,
1060
- shouldReplicate: true
1061
- });
1212
+ await publishToChannels(payload);
1062
1213
  }
1063
- async function publishToChannels(body, options2 = {
1064
- fromScaling: false,
1065
- shouldReplicate: true
1066
- }) {
1214
+ async function publishToChannels(body) {
1067
1215
  let deliveredSockets = 0;
1068
1216
  const deliveredChannels = [];
1069
1217
  for (const channel2 of body.channels) {
@@ -1073,9 +1221,7 @@ function createBroadcastWorkerRuntime(options) {
1073
1221
  }
1074
1222
  deliveredSockets += result.deliveredSockets;
1075
1223
  }
1076
- if (!options2.fromScaling && options2.shouldReplicate) {
1077
- await publishScalingEvent(body);
1078
- }
1224
+ await publishScalingEvent(body);
1079
1225
  return Object.freeze({
1080
1226
  deliveredChannels: Object.freeze(deliveredChannels),
1081
1227
  deliveredSockets
@@ -1084,19 +1230,27 @@ function createBroadcastWorkerRuntime(options) {
1084
1230
  async function handlePublishRequest(request) {
1085
1231
  const url = new URL(request.url);
1086
1232
  const match = url.pathname.match(/\/apps\/([^/]+)\/events$/);
1087
- const appId = normalizeRequiredString(match?.[1] ?? "", "Publish appId");
1233
+ const appId = normalizeWorkerRequiredString(match?.[1] ?? "", "Publish appId");
1088
1234
  const app = Object.values(appsByKey).find((candidate) => candidate.appId === appId);
1089
1235
  if (!app) {
1090
1236
  return new Response("App not found", { status: 404 });
1091
1237
  }
1092
- const bodyText = await request.text();
1238
+ let bodyText;
1239
+ try {
1240
+ bodyText = await readLimitedRequestText(request, options.config.worker.maxRequestBytes);
1241
+ } catch (error) {
1242
+ if (error instanceof BroadcastPayloadTooLargeError) {
1243
+ return new Response("Payload Too Large", { status: 413 });
1244
+ }
1245
+ throw error;
1246
+ }
1093
1247
  const bodyMd5 = createHash("md5").update(bodyText).digest("hex");
1094
1248
  if (url.searchParams.get("body_md5") !== bodyMd5) {
1095
1249
  return new Response("Invalid body signature", { status: 401 });
1096
1250
  }
1097
1251
  let authKey;
1098
1252
  try {
1099
- authKey = normalizeRequiredString(url.searchParams.get("auth_key") ?? "", "Publish auth_key");
1253
+ authKey = normalizeWorkerRequiredString(url.searchParams.get("auth_key") ?? "", "Publish auth_key");
1100
1254
  } catch (error) {
1101
1255
  const message = error instanceof Error ? error.message : "Invalid credentials";
1102
1256
  return new Response(message, { status: 401 });
@@ -1107,7 +1261,7 @@ function createBroadcastWorkerRuntime(options) {
1107
1261
  let authTimestamp;
1108
1262
  try {
1109
1263
  authTimestamp = Number.parseInt(
1110
- normalizeRequiredString(url.searchParams.get("auth_timestamp") ?? "", "Publish auth_timestamp"),
1264
+ normalizeWorkerRequiredString(url.searchParams.get("auth_timestamp") ?? "", "Publish auth_timestamp"),
1111
1265
  10
1112
1266
  );
1113
1267
  } catch (error) {
@@ -1124,8 +1278,8 @@ function createBroadcastWorkerRuntime(options) {
1124
1278
  let providedSignature;
1125
1279
  let expectedSignature;
1126
1280
  try {
1127
- providedSignature = normalizeRequiredString(url.searchParams.get("auth_signature") ?? "", "Publish auth_signature");
1128
- expectedSignature = createPusherSignature(
1281
+ providedSignature = normalizeWorkerRequiredString(url.searchParams.get("auth_signature") ?? "", "Publish auth_signature");
1282
+ expectedSignature = createWorkerPusherSignature(
1129
1283
  app.secret,
1130
1284
  request.method,
1131
1285
  url.pathname,
@@ -1135,12 +1289,12 @@ function createBroadcastWorkerRuntime(options) {
1135
1289
  const message = error instanceof Error ? error.message : "Invalid auth signature";
1136
1290
  return new Response(message, { status: 401 });
1137
1291
  }
1138
- if (!verifyPusherSignature(providedSignature, expectedSignature)) {
1292
+ if (!verifyWorkerPusherSignature(providedSignature, expectedSignature)) {
1139
1293
  return new Response("Invalid auth signature", { status: 401 });
1140
1294
  }
1141
1295
  let publishBody;
1142
1296
  try {
1143
- publishBody = normalizePublishBody(parseJsonObject(bodyText, "Publish body"));
1297
+ publishBody = normalizeWorkerPublishBody(parseJsonObject(bodyText, "Publish body"));
1144
1298
  } catch (error) {
1145
1299
  return new Response(error.message, { status: 400 });
1146
1300
  }
@@ -1149,9 +1303,6 @@ function createBroadcastWorkerRuntime(options) {
1149
1303
  result = await publishToChannels({
1150
1304
  ...publishBody,
1151
1305
  appId: app.appId
1152
- }, {
1153
- fromScaling: false,
1154
- shouldReplicate: true
1155
1306
  });
1156
1307
  } catch (error) {
1157
1308
  const message = error instanceof Error ? error.message : "Broadcast publish failed.";
@@ -1180,7 +1331,7 @@ function createBroadcastWorkerRuntime(options) {
1180
1331
  }
1181
1332
  });
1182
1333
  }
1183
- if (request.method.toUpperCase() === "GET" && url.pathname === options.config.worker.statsPath) {
1334
+ if (options.config.worker.statsEnabled && request.method.toUpperCase() === "GET" && url.pathname === options.config.worker.statsPath) {
1184
1335
  return new Response(JSON.stringify({
1185
1336
  ...this.getStats()
1186
1337
  }), {
@@ -1203,6 +1354,7 @@ function createBroadcastWorkerRuntime(options) {
1203
1354
  close: connection.close,
1204
1355
  subscribedChannels: /* @__PURE__ */ new Set(),
1205
1356
  realtimeSubscriptions: /* @__PURE__ */ new Map(),
1357
+ realtimeSubscriptionTokens: /* @__PURE__ */ new Map(),
1206
1358
  active: true,
1207
1359
  pendingMessage: Promise.resolve()
1208
1360
  });
@@ -1220,7 +1372,7 @@ function createBroadcastWorkerRuntime(options) {
1220
1372
  if (!socket.active || connectedSockets.get(socketId) !== socket) {
1221
1373
  return;
1222
1374
  }
1223
- const message = parseSocketMessage(rawMessage);
1375
+ const message = parseWorkerSocketMessage(rawMessage);
1224
1376
  if (message.event === "pusher:ping") {
1225
1377
  socket.send(pusherEvent("pusher:pong", {}));
1226
1378
  return;
@@ -1259,6 +1411,7 @@ function createBroadcastWorkerRuntime(options) {
1259
1411
  unsubscribeRealtimeSubscription(socket, subscriptionId, subscription);
1260
1412
  }
1261
1413
  socket.realtimeSubscriptions.clear();
1414
+ socket.realtimeSubscriptionTokens.clear();
1262
1415
  const channelsToCleanup = [...socket.subscribedChannels];
1263
1416
  const scalingCleanupTasks = channelsToCleanup.map((channel2) => {
1264
1417
  const removedPresenceMember = removeSubscriptionLocal(socket.app.appId, socket.socketId, channel2);
@@ -1281,11 +1434,8 @@ function createBroadcastWorkerRuntime(options) {
1281
1434
  await Promise.all(scalingCleanupTasks.map(async (task) => {
1282
1435
  await task();
1283
1436
  }));
1284
- }).catch((error) => {
1285
- logSocketMessageError(socket.socketId, error);
1286
- });
1287
- socket.pendingMessage = cleanupTask.catch(() => {
1288
1437
  });
1438
+ socket.pendingMessage = cleanupTask;
1289
1439
  },
1290
1440
  getStats() {
1291
1441
  return Object.freeze({
@@ -1316,64 +1466,6 @@ function createBroadcastWorkerRuntime(options) {
1316
1466
  }
1317
1467
  });
1318
1468
  }
1319
- function toNodeHeaders(headers) {
1320
- const normalized = new Headers();
1321
- for (const [key, value] of Object.entries(headers)) {
1322
- if (typeof value === "undefined") {
1323
- continue;
1324
- }
1325
- if (Array.isArray(value)) {
1326
- for (const item of value) {
1327
- normalized.append(key, item);
1328
- }
1329
- continue;
1330
- }
1331
- normalized.set(key, value);
1332
- }
1333
- return normalized;
1334
- }
1335
- function toNodeRequestUrl(request, fallbackHost) {
1336
- const path = request.url ?? "/";
1337
- const host = request.headers.host ?? fallbackHost;
1338
- return `http://${host}${path}`;
1339
- }
1340
- async function readNodeRequestBody(request) {
1341
- if (request.method === "GET" || request.method === "HEAD") {
1342
- return void 0;
1343
- }
1344
- const chunks = [];
1345
- for await (const chunk of request) {
1346
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1347
- }
1348
- if (chunks.length === 0) {
1349
- return void 0;
1350
- }
1351
- return Buffer.concat(chunks);
1352
- }
1353
- async function writeNodeResponse(response, value) {
1354
- response.statusCode = value.status;
1355
- response.statusMessage = value.statusText;
1356
- value.headers.forEach((headerValue, headerName) => {
1357
- response.setHeader(headerName, headerValue);
1358
- });
1359
- const body = await value.arrayBuffer();
1360
- response.end(Buffer.from(body));
1361
- }
1362
- function decodeNodeWebSocketMessage(message) {
1363
- if (typeof message === "string") {
1364
- return message;
1365
- }
1366
- if (message instanceof ArrayBuffer) {
1367
- return new TextDecoder().decode(new Uint8Array(message));
1368
- }
1369
- if (Array.isArray(message)) {
1370
- return Buffer.concat(message).toString("utf8");
1371
- }
1372
- if (message instanceof Uint8Array) {
1373
- return Buffer.from(message).toString("utf8");
1374
- }
1375
- return String(message);
1376
- }
1377
1469
  async function handleSubscribeFailure(runtime, subscribeError) {
1378
1470
  try {
1379
1471
  await runtime.close();
@@ -1412,11 +1504,10 @@ async function startBroadcastWorker(runtimeBindings) {
1412
1504
  }
1413
1505
  });
1414
1506
  if (scalingConfig) {
1415
- scalingUnsubscribe = await scalingConfig.adapter.subscribe(scalingConfig.eventChannel, (payload) => {
1416
- void runtime.receiveScalingMessage(payload).catch((error) => {
1417
- logScalingMessageError(error);
1418
- });
1419
- }).catch((subscribeError) => handleSubscribeFailure(runtime, subscribeError));
1507
+ scalingUnsubscribe = await scalingConfig.adapter.subscribe(
1508
+ scalingConfig.eventChannel,
1509
+ createScalingMessageListener(runtime)
1510
+ ).catch((subscribeError) => handleSubscribeFailure(runtime, subscribeError));
1420
1511
  }
1421
1512
  const bun = globalThis.Bun;
1422
1513
  const appsByKey = buildWorkerApps(config);
@@ -1435,6 +1526,9 @@ async function startBroadcastWorker(runtimeBindings) {
1435
1526
  if (!app) {
1436
1527
  return new Response("Unknown app key", { status: 401 });
1437
1528
  }
1529
+ if (!isAllowedWorkerOrigin(request, config.worker.allowedOrigins)) {
1530
+ return new Response("Forbidden", { status: 403 });
1531
+ }
1438
1532
  const upgraded = wsServer2.upgrade(request, {
1439
1533
  data: {
1440
1534
  socketId: createSocketId(),
@@ -1462,6 +1556,12 @@ async function startBroadcastWorker(runtimeBindings) {
1462
1556
  });
1463
1557
  },
1464
1558
  message(socket, message) {
1559
+ const messageBytes = typeof message === "string" ? Buffer.byteLength(message) : message.byteLength;
1560
+ if (messageBytes > config.worker.maxMessageBytes) {
1561
+ runtime.disconnectWebSocket(socket.data.socketId);
1562
+ socket.close(1009, "Message too large");
1563
+ return;
1564
+ }
1465
1565
  const value = typeof message === "string" ? message : new TextDecoder().decode(message);
1466
1566
  void runtime.receiveWebSocketMessage(socket.data.socketId, value).catch((error) => {
1467
1567
  logSocketMessageError(socket.data.socketId, error);
@@ -1497,7 +1597,7 @@ async function startBroadcastWorker(runtimeBindings) {
1497
1597
  throw new Error("[@holo-js/broadcast] Node runtime websocket module is missing WebSocketServer export.");
1498
1598
  }
1499
1599
  const requestConnectionInfo = /* @__PURE__ */ new WeakMap();
1500
- const wsServer = new WebSocketServer({ noServer: true });
1600
+ const wsServer = new WebSocketServer({ noServer: true, maxPayload: config.worker.maxMessageBytes });
1501
1601
  wsServer.on("connection", (socket, request) => {
1502
1602
  const connectionInfo = requestConnectionInfo.get(request);
1503
1603
  const socketId = connectionInfo.socketId;
@@ -1511,6 +1611,12 @@ async function startBroadcastWorker(runtimeBindings) {
1511
1611
  }
1512
1612
  });
1513
1613
  socket.on("message", (message) => {
1614
+ const messageBytes = getNodeWebSocketMessageBytes(message);
1615
+ if (messageBytes > config.worker.maxMessageBytes) {
1616
+ runtime.disconnectWebSocket(socketId);
1617
+ socket.close(1009, "Message too large");
1618
+ return;
1619
+ }
1514
1620
  const value = decodeNodeWebSocketMessage(message);
1515
1621
  void runtime.receiveWebSocketMessage(socketId, value).catch((error) => {
1516
1622
  logSocketMessageError(socketId, error);
@@ -1524,7 +1630,13 @@ async function startBroadcastWorker(runtimeBindings) {
1524
1630
  });
1525
1631
  const httpServer = createServer(async (request, response) => {
1526
1632
  const requestUrl = toNodeRequestUrl(request, `${config.worker.host}:${config.worker.port}`);
1527
- const requestBody = await readNodeRequestBody(request);
1633
+ let requestBody;
1634
+ try {
1635
+ requestBody = await readNodeRequestBody(request, config.worker.maxRequestBytes);
1636
+ } catch (error) {
1637
+ writeNodeRequestBodyError(response, error);
1638
+ return;
1639
+ }
1528
1640
  const requestInit = {
1529
1641
  method: request.method,
1530
1642
  headers: toNodeHeaders(request.headers)
@@ -1551,6 +1663,12 @@ async function startBroadcastWorker(runtimeBindings) {
1551
1663
  socket.destroy();
1552
1664
  return;
1553
1665
  }
1666
+ const originRequest = new Request(requestUrl, { headers: toNodeHeaders(request.headers) });
1667
+ if (!isAllowedWorkerOrigin(originRequest, config.worker.allowedOrigins)) {
1668
+ socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
1669
+ socket.destroy();
1670
+ return;
1671
+ }
1554
1672
  requestConnectionInfo.set(request, {
1555
1673
  socketId: createSocketId(),
1556
1674
  app,
@@ -1603,19 +1721,37 @@ var workerInternals = {
1603
1721
  buildWorkerApps,
1604
1722
  createScalingNodeId,
1605
1723
  createRedisScalingAdapter,
1606
- createPusherSignature,
1607
- verifyPusherSignature,
1724
+ createPusherSignature: createWorkerPusherSignature,
1725
+ verifyPusherSignature: verifyWorkerPusherSignature,
1608
1726
  createSocketId,
1609
1727
  resolveRedisScalingConnection,
1610
1728
  resolveScalingEventChannel,
1611
- normalizePublishBody,
1612
- parseChannelKind,
1729
+ normalizePublishBody: normalizeWorkerPublishBody,
1730
+ parseChannelKind: parseWorkerChannelKind,
1613
1731
  parsePresenceHashMembers,
1614
- parseSocketMessage
1732
+ parseSocketMessage: parseWorkerSocketMessage,
1733
+ isAllowedWorkerOrigin,
1734
+ readLimitedRequestText,
1735
+ normalizeRealtimeAction,
1736
+ normalizeRealtimeArgs,
1737
+ parseRealtimeSocketMessage,
1738
+ logSocketMessageError,
1739
+ logScalingMessageError,
1740
+ logSocketCleanupError,
1741
+ logRealtimeSubscriptionCleanupError,
1742
+ safeEqual,
1743
+ signChannelAuth,
1744
+ parseClientChannelAuth,
1745
+ parseSignedChannelData,
1746
+ verifyClientChannelAuth,
1747
+ resolvePresenceMemberId,
1748
+ resolveRealtimeErrorStatus,
1749
+ resolveRealtimeErrorCode,
1750
+ resolveRealtimeErrorKind,
1751
+ writeNodeRequestBodyError
1615
1752
  };
1616
1753
 
1617
1754
  // src/index.ts
1618
- import { defineBroadcastConfig } from "@holo-js/config";
1619
1755
  var broadcastPackage = Object.freeze({
1620
1756
  authorizeBroadcastChannel,
1621
1757
  broadcast,
@@ -1658,9 +1794,12 @@ export {
1658
1794
  getBroadcastRuntime,
1659
1795
  getBroadcastRuntimeBindings,
1660
1796
  getRegisteredBroadcastDriver,
1797
+ holoBroadcastDefaults,
1661
1798
  isBroadcastDefinition,
1662
1799
  isChannelDefinition,
1663
1800
  listRegisteredBroadcastDrivers,
1801
+ loadBroadcastPluginDrivers,
1802
+ normalizeBroadcastConfig,
1664
1803
  parseBroadcastAuthEndpointPayload,
1665
1804
  presenceChannel,
1666
1805
  privateChannel,
@@ -1668,6 +1807,7 @@ export {
1668
1807
  renderBroadcastAuthResponse,
1669
1808
  renderBroadcastClientConfigResponse,
1670
1809
  resetBroadcastDriverRegistry,
1810
+ resetBroadcastPluginDrivers,
1671
1811
  resetBroadcastRuntime,
1672
1812
  resolveBroadcastChannelGuard,
1673
1813
  resolveBroadcastClientConfig,