@holo-js/broadcast 0.1.3 → 0.1.5

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/auth.mjs CHANGED
@@ -5,9 +5,9 @@ import {
5
5
  renderBroadcastAuthResponse,
6
6
  resolveBroadcastWhisperSchema,
7
7
  validateBroadcastWhisperPayload
8
- } from "./chunk-5XRABYQH.mjs";
9
- import "./chunk-742LGR5P.mjs";
10
- import "./chunk-QW6MHEWS.mjs";
8
+ } from "./chunk-I3KE6HDH.mjs";
9
+ import "./chunk-QYXS4X72.mjs";
10
+ import "./chunk-DHKMBH25.mjs";
11
11
  export {
12
12
  authorizeBroadcastChannel,
13
13
  broadcastAuthInternals,
@@ -33,7 +33,13 @@ function normalizeDelayValue(value) {
33
33
  return value;
34
34
  }
35
35
  function normalizeJsonValue(value, path) {
36
- if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
36
+ if (typeof value === "number") {
37
+ if (!Number.isFinite(value)) {
38
+ throw new Error(`[Holo Broadcast] ${path} must be JSON-serializable.`);
39
+ }
40
+ return value;
41
+ }
42
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
37
43
  return value;
38
44
  }
39
45
  if (Array.isArray(value)) {
@@ -92,6 +98,16 @@ function normalizePatternSegment(segment, label) {
92
98
  }
93
99
  return segment;
94
100
  }
101
+ function normalizeChannelTargetParamValue(pattern, key, value) {
102
+ const normalized = String(value).trim();
103
+ if (!normalized) {
104
+ throw new Error(`[Holo Broadcast] Channel target param "${key}" for "${pattern}" must be a non-empty channel segment.`);
105
+ }
106
+ if (!/^[A-Za-z0-9_-]+$/.test(normalized)) {
107
+ throw new Error(`[Holo Broadcast] Channel target param "${key}" for "${pattern}" contains invalid segment "${normalized}".`);
108
+ }
109
+ return normalized;
110
+ }
95
111
  function extractChannelPatternParamNames(pattern) {
96
112
  const normalized = normalizeChannelPattern(pattern, "Channel pattern");
97
113
  const params = normalized.split(".").map((segment) => segment.match(/^\{([A-Za-z_][A-Za-z0-9_]*)\}$/)?.[1]).filter((value) => typeof value === "string");
@@ -115,7 +131,7 @@ function normalizeTargetParams(pattern, params) {
115
131
  if (!normalizedKey) {
116
132
  throw new Error("[Holo Broadcast] Channel target params must not include empty keys.");
117
133
  }
118
- return [normalizedKey, String(value)];
134
+ return [normalizedKey, normalizeChannelTargetParamValue(pattern, normalizedKey, value)];
119
135
  });
120
136
  const provided = Object.freeze(Object.fromEntries(providedEntries));
121
137
  const providedNames = Object.keys(provided);
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  getBroadcastRuntimeBindings
3
- } from "./chunk-742LGR5P.mjs";
3
+ } from "./chunk-QYXS4X72.mjs";
4
4
  import {
5
5
  isChannelDefinition
6
- } from "./chunk-QW6MHEWS.mjs";
6
+ } from "./chunk-DHKMBH25.mjs";
7
7
 
8
8
  // src/auth.ts
9
9
  import { resolve } from "path";
@@ -41,7 +41,13 @@ function normalizeLookupChannel(channel, label) {
41
41
  return normalized;
42
42
  }
43
43
  function normalizeJsonValue(value, path) {
44
- if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
44
+ if (typeof value === "number") {
45
+ if (!Number.isFinite(value)) {
46
+ throw new Error(`[@holo-js/broadcast] ${path} must be JSON-serializable.`);
47
+ }
48
+ return value;
49
+ }
50
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
45
51
  return value;
46
52
  }
47
53
  if (Array.isArray(value)) {
@@ -156,7 +162,15 @@ async function loadChannelDefinitions(bindings) {
156
162
  })();
157
163
  getRuntimeState().byBindings ??= /* @__PURE__ */ new WeakMap();
158
164
  getRuntimeState().byBindings.set(bindings, pending);
159
- return await pending;
165
+ try {
166
+ return await pending;
167
+ } catch (error) {
168
+ const cache = getRuntimeState().byBindings;
169
+ if (cache.get(bindings) === pending) {
170
+ cache.delete(bindings);
171
+ }
172
+ throw error;
173
+ }
160
174
  }
161
175
  function matchPattern(pattern, channel) {
162
176
  const patternSegments = pattern.split(".");
@@ -2,7 +2,7 @@ import {
2
2
  formatChannelPattern,
3
3
  isBroadcastDefinition,
4
4
  normalizeBroadcastDefinition
5
- } from "./chunk-QW6MHEWS.mjs";
5
+ } from "./chunk-DHKMBH25.mjs";
6
6
 
7
7
  // src/runtime.ts
8
8
  import { randomUUID } from "crypto";
@@ -114,7 +114,13 @@ function isRecord(value) {
114
114
  return !!value && typeof value === "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
115
115
  }
116
116
  function normalizeJsonValue(value, path) {
117
- if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
117
+ if (typeof value === "number") {
118
+ if (!Number.isFinite(value)) {
119
+ throw new Error(`[@holo-js/broadcast] ${path} must be JSON-serializable.`);
120
+ }
121
+ return value;
122
+ }
123
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
118
124
  return value;
119
125
  }
120
126
  if (Array.isArray(value)) {
@@ -13,7 +13,7 @@ import {
13
13
  normalizeChannelPattern,
14
14
  presenceChannel,
15
15
  privateChannel
16
- } from "./chunk-QW6MHEWS.mjs";
16
+ } from "./chunk-DHKMBH25.mjs";
17
17
  export {
18
18
  broadcastInternals,
19
19
  channel,
package/dist/index.d.ts CHANGED
@@ -107,6 +107,7 @@ declare function parseSocketMessage(rawMessage: string): {
107
107
  };
108
108
  declare function normalizePublishBody(value: unknown): PublishBody;
109
109
  declare function createPusherSignature(secret: string, method: string, pathname: string, params: URLSearchParams): string;
110
+ declare function verifyPusherSignature(providedSignature: string, expectedSignature: string): boolean;
110
111
  declare function parseChannelKind(channel: string): {
111
112
  readonly kind: 'public' | 'private' | 'presence';
112
113
  readonly canonical: string;
@@ -135,6 +136,7 @@ declare const workerInternals: {
135
136
  createScalingNodeId: typeof createScalingNodeId;
136
137
  createRedisScalingAdapter: typeof createRedisScalingAdapter;
137
138
  createPusherSignature: typeof createPusherSignature;
139
+ verifyPusherSignature: typeof verifyPusherSignature;
138
140
  createSocketId: typeof createSocketId;
139
141
  resolveRedisScalingConnection: typeof resolveRedisScalingConnection;
140
142
  resolveScalingEventChannel: typeof resolveScalingEventChannel;
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  renderBroadcastAuthResponse,
6
6
  resolveBroadcastWhisperSchema,
7
7
  validateBroadcastWhisperPayload
8
- } from "./chunk-5XRABYQH.mjs";
8
+ } from "./chunk-I3KE6HDH.mjs";
9
9
  import {
10
10
  broadcast,
11
11
  broadcastRaw,
@@ -19,7 +19,7 @@ import {
19
19
  registerBroadcastDriver,
20
20
  resetBroadcastDriverRegistry,
21
21
  resetBroadcastRuntime
22
- } from "./chunk-742LGR5P.mjs";
22
+ } from "./chunk-QYXS4X72.mjs";
23
23
  import {
24
24
  broadcastInternals,
25
25
  channel,
@@ -29,10 +29,10 @@ import {
29
29
  isChannelDefinition,
30
30
  presenceChannel,
31
31
  privateChannel
32
- } from "./chunk-QW6MHEWS.mjs";
32
+ } from "./chunk-DHKMBH25.mjs";
33
33
 
34
34
  // src/worker.ts
35
- import { createHash, createHmac, randomInt, randomUUID } from "crypto";
35
+ import { createHash, createHmac, randomInt, randomUUID, timingSafeEqual } from "crypto";
36
36
  import { createServer } from "http";
37
37
  var MAX_PUBLISH_TIMESTAMP_SKEW_SECONDS = 300;
38
38
  function normalizeRequiredString(value, label) {
@@ -102,6 +102,12 @@ ${pathname}
102
102
  ${sorted}`;
103
103
  return createHmac("sha256", secret).update(payload).digest("hex");
104
104
  }
105
+ function verifyPusherSignature(providedSignature, expectedSignature) {
106
+ if (providedSignature.length !== expectedSignature.length || !/^[a-f0-9]+$/i.test(providedSignature) || !/^[a-f0-9]+$/i.test(expectedSignature)) {
107
+ return false;
108
+ }
109
+ return timingSafeEqual(Buffer.from(providedSignature, "hex"), Buffer.from(expectedSignature, "hex"));
110
+ }
105
111
  function logSocketMessageError(socketId, error) {
106
112
  const message = error instanceof Error ? error.message : String(error);
107
113
  console.error(`[@holo-js/broadcast] WebSocket message handling failed for socket "${socketId}": ${message}`);
@@ -918,7 +924,7 @@ function createBroadcastWorkerRuntime(options) {
918
924
  const message = error instanceof Error ? error.message : "Invalid auth signature";
919
925
  return new Response(message, { status: 401 });
920
926
  }
921
- if (providedSignature !== expectedSignature) {
927
+ if (!verifyPusherSignature(providedSignature, expectedSignature)) {
922
928
  return new Response("Invalid auth signature", { status: 401 });
923
929
  }
924
930
  let publishBody;
@@ -1378,6 +1384,7 @@ var workerInternals = {
1378
1384
  createScalingNodeId,
1379
1385
  createRedisScalingAdapter,
1380
1386
  createPusherSignature,
1387
+ verifyPusherSignature,
1381
1388
  createSocketId,
1382
1389
  resolveRedisScalingConnection,
1383
1390
  resolveScalingEventChannel,
package/dist/runtime.mjs CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  getBroadcastRuntime,
7
7
  getBroadcastRuntimeBindings,
8
8
  resetBroadcastRuntime
9
- } from "./chunk-742LGR5P.mjs";
10
- import "./chunk-QW6MHEWS.mjs";
9
+ } from "./chunk-QYXS4X72.mjs";
10
+ import "./chunk-DHKMBH25.mjs";
11
11
  export {
12
12
  broadcast,
13
13
  broadcastRaw,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holo-js/broadcast",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Holo-JS Framework - broadcast contracts, channel definitions, and driver registration seams",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,16 +34,16 @@
34
34
  "scripts": {
35
35
  "build": "tsup",
36
36
  "stub": "tsup",
37
- "typecheck": "tsc -p tsconfig.json --noEmit",
37
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.type-tests.json --noEmit",
38
38
  "test": "vitest --run"
39
39
  },
40
40
  "dependencies": {
41
- "@holo-js/config": "^0.1.3",
42
- "@holo-js/validation": "^0.1.3",
41
+ "@holo-js/config": "^0.1.5",
42
+ "@holo-js/validation": "^0.1.5",
43
43
  "ws": "^8.18.3"
44
44
  },
45
45
  "peerDependencies": {
46
- "ioredis": "catalog:"
46
+ "ioredis": "^5.4.2"
47
47
  },
48
48
  "peerDependenciesMeta": {
49
49
  "ioredis": {
@@ -53,9 +53,9 @@
53
53
  "devDependencies": {
54
54
  "@types/node": "^22.10.2",
55
55
  "@types/ws": "^8.18.1",
56
- "ioredis": "catalog:",
56
+ "ioredis": "^5.4.2",
57
57
  "tsup": "^8.3.5",
58
58
  "typescript": "^5.7.2",
59
- "vitest": "^2.1.8"
59
+ "vitest": "^4.1.5"
60
60
  }
61
61
  }