@holo-js/broadcast 0.1.4 → 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 +3 -3
- package/dist/{chunk-QW6MHEWS.mjs → chunk-DHKMBH25.mjs} +18 -2
- package/dist/{chunk-5XRABYQH.mjs → chunk-I3KE6HDH.mjs} +18 -4
- package/dist/{chunk-742LGR5P.mjs → chunk-QYXS4X72.mjs} +8 -2
- package/dist/contracts.mjs +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +12 -5
- package/dist/runtime.mjs +2 -2
- package/package.json +7 -7
package/dist/auth.mjs
CHANGED
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
renderBroadcastAuthResponse,
|
|
6
6
|
resolveBroadcastWhisperSchema,
|
|
7
7
|
validateBroadcastWhisperPayload
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
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 (
|
|
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,
|
|
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-
|
|
3
|
+
} from "./chunk-QYXS4X72.mjs";
|
|
4
4
|
import {
|
|
5
5
|
isChannelDefinition
|
|
6
|
-
} from "./chunk-
|
|
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 (
|
|
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
|
-
|
|
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-
|
|
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 (
|
|
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)) {
|
package/dist/contracts.mjs
CHANGED
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-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
10
|
-
import "./chunk-
|
|
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
|
+
"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.
|
|
42
|
-
"@holo-js/validation": "^0.1.
|
|
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": "
|
|
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": "
|
|
56
|
+
"ioredis": "^5.4.2",
|
|
57
57
|
"tsup": "^8.3.5",
|
|
58
58
|
"typescript": "^5.7.2",
|
|
59
|
-
"vitest": "^
|
|
59
|
+
"vitest": "^4.1.5"
|
|
60
60
|
}
|
|
61
61
|
}
|