@hyperfrontend/features 0.1.0 → 0.2.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/CHANGELOG.md +17 -0
- package/_dependencies/@hyperfrontend/builder/bundle/dependencies/index.cjs.js +1 -0
- package/_dependencies/@hyperfrontend/builder/bundle/dependencies/index.esm.js +1 -0
- package/_dependencies/@hyperfrontend/builder/bundle/dependencies/worker/index.cjs.js +1 -0
- package/_dependencies/@hyperfrontend/builder/bundle/dependencies/worker/index.esm.js +1 -0
- package/_dependencies/@hyperfrontend/builder/bundle/index.cjs.js +12 -10
- package/_dependencies/@hyperfrontend/builder/bundle/index.esm.js +14 -12
- package/_dependencies/@hyperfrontend/builder/bundle/rollup/index.cjs.js +2 -0
- package/_dependencies/@hyperfrontend/builder/bundle/rollup/index.esm.js +2 -0
- package/_dependencies/@hyperfrontend/builder/bundle/rollup/worker/index.cjs.js +2 -0
- package/_dependencies/@hyperfrontend/builder/bundle/rollup/worker/index.esm.js +2 -0
- package/_dependencies/@hyperfrontend/builder/index.cjs.js +87 -53
- package/_dependencies/@hyperfrontend/builder/index.esm.js +89 -55
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/promise/index.cjs.js +4 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/promise/index.esm.js +3 -1
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/reflect/index.cjs.js +10 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/reflect/index.esm.js +6 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/timers/index.cjs.js +5 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/timers/index.esm.js +5 -1
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/typed-arrays/index.cjs.js +2 -2
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/typed-arrays/index.esm.js +2 -2
- package/_dependencies/@hyperfrontend/network-protocol/browser/channel/index.cjs.js +5 -19
- package/_dependencies/@hyperfrontend/network-protocol/browser/channel/index.esm.js +1 -15
- package/_dependencies/@hyperfrontend/network-protocol/browser/data/index.cjs.js +15 -23
- package/_dependencies/@hyperfrontend/network-protocol/browser/data/index.esm.js +7 -15
- package/_dependencies/@hyperfrontend/network-protocol/browser/packet/index.cjs.js +6 -14
- package/_dependencies/@hyperfrontend/network-protocol/browser/packet/index.esm.js +7 -15
- package/_dependencies/@hyperfrontend/network-protocol/browser/receiver/index.cjs.js +4 -18
- package/_dependencies/@hyperfrontend/network-protocol/browser/receiver/index.esm.js +1 -15
- package/_dependencies/@hyperfrontend/network-protocol/browser/sender/index.cjs.js +5 -19
- package/_dependencies/@hyperfrontend/network-protocol/browser/sender/index.esm.js +2 -16
- package/_dependencies/@hyperfrontend/network-protocol/browser/v1/index.cjs.js +16 -24
- package/_dependencies/@hyperfrontend/network-protocol/browser/v1/index.esm.js +7 -15
- package/_dependencies/@hyperfrontend/network-protocol/browser/v2/index.cjs.js +16 -24
- package/_dependencies/@hyperfrontend/network-protocol/browser/v2/index.esm.js +7 -15
- package/_dependencies/@hyperfrontend/network-protocol/node/channel/index.cjs.js +3 -17
- package/_dependencies/@hyperfrontend/network-protocol/node/channel/index.esm.js +1 -15
- package/_dependencies/@hyperfrontend/network-protocol/node/data/index.cjs.js +6 -14
- package/_dependencies/@hyperfrontend/network-protocol/node/data/index.esm.js +7 -15
- package/_dependencies/@hyperfrontend/network-protocol/node/packet/index.cjs.js +6 -14
- package/_dependencies/@hyperfrontend/network-protocol/node/packet/index.esm.js +7 -15
- package/_dependencies/@hyperfrontend/network-protocol/node/receiver/index.cjs.js +3 -17
- package/_dependencies/@hyperfrontend/network-protocol/node/receiver/index.esm.js +1 -15
- package/_dependencies/@hyperfrontend/network-protocol/node/sender/index.cjs.js +2 -16
- package/_dependencies/@hyperfrontend/network-protocol/node/sender/index.esm.js +2 -16
- package/_dependencies/@hyperfrontend/network-protocol/node/v1/index.cjs.js +6 -14
- package/_dependencies/@hyperfrontend/network-protocol/node/v1/index.esm.js +7 -15
- package/_dependencies/@hyperfrontend/network-protocol/node/v2/index.cjs.js +6 -14
- package/_dependencies/@hyperfrontend/network-protocol/node/v2/index.esm.js +7 -15
- package/_dependencies/@hyperfrontend/nexus/index.cjs.js +49 -19
- package/_dependencies/@hyperfrontend/nexus/index.esm.js +49 -19
- package/_dependencies/@hyperfrontend/project-scope/core/fs/index.cjs.js +62 -0
- package/_dependencies/@hyperfrontend/project-scope/core/fs/index.esm.js +60 -2
- package/_shared/generators/feature/generate-feature-module/index.esm.js +11 -6
- package/_shared/generators/metadata/generate-metadata/index.esm.js +1 -0
- package/_shared/shared/control/index.cjs.js +12 -2
- package/_shared/shared/control/index.esm.js +12 -2
- package/_shared/shared/request/index.cjs.js +91 -0
- package/_shared/shared/request/index.esm.js +88 -0
- package/_shared/shared/shutdown/index.esm.js +12 -0
- package/bin/hf.js +643 -70
- package/bundle/host/index.iife.js +290 -4041
- package/bundle/host/index.iife.min.js +1 -1
- package/bundle/host/index.umd.js +290 -4041
- package/bundle/host/index.umd.min.js +1 -1
- package/bundle/hostee/index.iife.js +215 -2893
- package/bundle/hostee/index.iife.min.js +1 -1
- package/bundle/hostee/index.umd.js +215 -2893
- package/bundle/hostee/index.umd.min.js +1 -1
- package/cli/args.d.ts +2 -0
- package/cli/args.d.ts.map +1 -1
- package/cli/commands/build.d.ts +8 -5
- package/cli/commands/build.d.ts.map +1 -1
- package/cli/commands/dev.d.ts +7 -2
- package/cli/commands/dev.d.ts.map +1 -1
- package/cli/config/resolve.d.ts +3 -1
- package/cli/config/resolve.d.ts.map +1 -1
- package/cli/index.cjs.js +643 -70
- package/cli/index.d.ts +21 -10
- package/cli/index.esm.js +591 -60
- package/cli/usage.d.ts +1 -1
- package/cli/usage.d.ts.map +1 -1
- package/generators/feature/generate-feature-module.d.ts.map +1 -1
- package/generators/index.cjs.js +435 -42
- package/generators/index.d.ts +9 -8
- package/generators/index.esm.js +404 -30
- package/generators/metadata/generate-metadata.d.ts +4 -4
- package/generators/metadata/generate-metadata.d.ts.map +1 -1
- package/generators/shell/connector-types.d.ts +19 -0
- package/generators/shell/connector-types.d.ts.map +1 -0
- package/generators/shell/generate-shell.d.ts +5 -4
- package/generators/shell/generate-shell.d.ts.map +1 -1
- package/generators/shell/schema-type.d.ts +20 -0
- package/generators/shell/schema-type.d.ts.map +1 -0
- package/generators/shell/source-literal.d.ts +28 -0
- package/generators/shell/source-literal.d.ts.map +1 -1
- package/host/create-shell.d.ts +4 -1
- package/host/create-shell.d.ts.map +1 -1
- package/host/display-modes/dialog.d.ts +1 -1
- package/host/display-modes/dialog.d.ts.map +1 -1
- package/host/display-modes/embedded.d.ts +1 -1
- package/host/display-modes/embedded.d.ts.map +1 -1
- package/host/index.cjs.js +150 -30
- package/host/index.d.ts +53 -38
- package/host/index.d.ts.map +1 -1
- package/host/index.esm.js +129 -9
- package/host/lifecycle.d.ts.map +1 -1
- package/host/plugins.d.ts +1 -34
- package/host/plugins.d.ts.map +1 -1
- package/host/types.d.ts +49 -0
- package/host/types.d.ts.map +1 -1
- package/hostee/index.cjs.js +54 -9
- package/hostee/index.d.ts +41 -1
- package/hostee/index.d.ts.map +1 -1
- package/hostee/index.esm.js +51 -6
- package/hostee/lifecycle.d.ts.map +1 -1
- package/hostee/types.d.ts +40 -0
- package/hostee/types.d.ts.map +1 -1
- package/index.cjs.js +32 -1
- package/index.d.ts +89 -3
- package/index.d.ts.map +1 -1
- package/index.esm.js +32 -1
- package/nx/executors/build/index.cjs.js +14975 -137
- package/nx/executors/build/index.esm.js +14935 -115
- package/nx/executors/serve/executor.d.ts.map +1 -1
- package/nx/executors/serve/index.cjs.js +6594 -80
- package/nx/executors/serve/index.esm.js +6529 -44
- package/nx/generators/feature/index.cjs.js +8751 -108
- package/nx/generators/feature/index.esm.js +8711 -81
- package/package.json +15 -5
- package/server/debug-ui/index.d.ts +2 -0
- package/server/debug-ui/index.d.ts.map +1 -0
- package/server/debug-ui/index.html +15 -0
- package/server/debug-ui/index.iife.js +427 -0
- package/server/debug-ui/index.iife.min.js +1 -0
- package/server/dev-server.d.ts.map +1 -1
- package/server/index.cjs.js +78 -10
- package/server/index.esm.js +78 -11
- package/server/module-dir.d.ts +17 -0
- package/server/module-dir.d.ts.map +1 -0
- package/server/module-dir.stub.d.ts +15 -0
- package/server/module-dir.stub.d.ts.map +1 -0
- package/shared/contract.d.ts +1 -1
- package/shared/contract.d.ts.map +1 -1
- package/shared/control.d.ts +4 -0
- package/shared/control.d.ts.map +1 -1
- package/shared/invert-contract.d.ts +20 -0
- package/shared/invert-contract.d.ts.map +1 -0
- package/shared/request.d.ts +68 -0
- package/shared/request.d.ts.map +1 -0
- package/{nx/shared → shared}/shutdown.d.ts +3 -2
- package/shared/shutdown.d.ts.map +1 -0
- package/shared/types.d.ts +72 -1
- package/shared/types.d.ts.map +1 -1
- package/_shared/nx/shared/context/index.cjs.js +0 -18
- package/_shared/nx/shared/context/index.esm.js +0 -16
- package/nx/shared/shutdown.d.ts.map +0 -1
- package/server/debug-ui/bootstrap.d.ts +0 -2
- package/server/debug-ui/bootstrap.d.ts.map +0 -1
|
@@ -4,120 +4,26 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.HyperfrontendFeaturesHostee = {}));
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Safe copies of Error built-ins via factory functions.
|
|
9
|
-
*
|
|
10
|
-
* Since constructors cannot be safely captured via Object.assign, this module
|
|
11
|
-
* provides factory functions that use Reflect.construct internally.
|
|
12
|
-
*
|
|
13
|
-
* These references are captured at module initialization time to protect against
|
|
14
|
-
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
15
|
-
*
|
|
16
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/error
|
|
17
|
-
*/
|
|
18
7
|
const _Error = globalThis.Error;
|
|
19
|
-
const _Reflect$
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* Use this instead of `new Error()`.
|
|
23
|
-
*
|
|
24
|
-
* @param message - Optional error message.
|
|
25
|
-
* @param options - Optional error options.
|
|
26
|
-
* @returns A new Error instance.
|
|
27
|
-
*
|
|
28
|
-
* @example Creating Error instances
|
|
29
|
-
* ```typescript
|
|
30
|
-
* const error = createError('Operation failed')
|
|
31
|
-
* // With cause for error chaining
|
|
32
|
-
* const wrapped = createError('Request failed', { cause: originalError })
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
const createError = (message, options) => _Reflect$9.construct(_Error, [message, options]);
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Safe copies of Object built-in methods.
|
|
39
|
-
*
|
|
40
|
-
* These references are captured at module initialization time to protect against
|
|
41
|
-
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
42
|
-
*
|
|
43
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/object
|
|
44
|
-
*/
|
|
8
|
+
const _Reflect$a = globalThis.Reflect;
|
|
9
|
+
const createError = (message, options) => _Reflect$a.construct(_Error, [message, options]);
|
|
10
|
+
|
|
45
11
|
const _Object = globalThis.Object;
|
|
46
|
-
const _Reflect$
|
|
12
|
+
const _Reflect$9 = globalThis.Reflect;
|
|
47
13
|
const _ObjectPrototype = _Object.prototype;
|
|
48
14
|
const _hasOwnProperty = _ObjectPrototype.hasOwnProperty;
|
|
49
|
-
/**
|
|
50
|
-
* (Safe copy) Prevents modification of existing property attributes and values,
|
|
51
|
-
* and prevents the addition of new properties.
|
|
52
|
-
*/
|
|
53
15
|
const freeze = _Object.freeze;
|
|
54
|
-
/**
|
|
55
|
-
* (Safe copy) Returns the names of the enumerable string properties and methods of an object.
|
|
56
|
-
*/
|
|
57
16
|
const keys = _Object.keys;
|
|
58
|
-
/**
|
|
59
|
-
* (Safe copy) Returns an array of key/values of the enumerable own properties of an object.
|
|
60
|
-
*/
|
|
61
17
|
const entries = _Object.entries;
|
|
62
|
-
|
|
63
|
-
* (Safe copy) Adds a property to an object, or modifies attributes of an existing property.
|
|
64
|
-
*/
|
|
18
|
+
const values = _Object.values;
|
|
65
19
|
const defineProperty = _Object.defineProperty;
|
|
66
|
-
/**
|
|
67
|
-
* (Safe copy) Sets the prototype of a specified object o to object proto or null.
|
|
68
|
-
*/
|
|
69
20
|
const setPrototypeOf = _Object.setPrototypeOf;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
* Checks if an object has a property as its own (not inherited) property.
|
|
73
|
-
*
|
|
74
|
-
* @param obj - The object to check.
|
|
75
|
-
* @param key - The property key to check.
|
|
76
|
-
* @returns True if the object has the property as its own property.
|
|
77
|
-
*
|
|
78
|
-
* @example Checking own properties
|
|
79
|
-
* ```typescript
|
|
80
|
-
* const user = { name: 'Alice' }
|
|
81
|
-
* hasOwn(user, 'name') // => true
|
|
82
|
-
* hasOwn(user, 'toString') // => false (inherited from prototype)
|
|
83
|
-
* ```
|
|
84
|
-
*/
|
|
85
|
-
const hasOwn = (obj, key) => _Reflect$8.apply(_hasOwnProperty, obj, [key]);
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Safe copies of Math built-in methods.
|
|
89
|
-
*
|
|
90
|
-
* These references are captured at module initialization time to protect against
|
|
91
|
-
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
92
|
-
*
|
|
93
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/math
|
|
94
|
-
*/
|
|
21
|
+
const hasOwn = (obj, key) => _Reflect$9.apply(_hasOwnProperty, obj, [key]);
|
|
22
|
+
|
|
95
23
|
const _Math = globalThis.Math;
|
|
96
|
-
/**
|
|
97
|
-
* (Safe copy) Returns the absolute value of a number.
|
|
98
|
-
*/
|
|
99
24
|
const abs = _Math.abs;
|
|
100
|
-
/**
|
|
101
|
-
* (Safe copy) Returns a pseudo-random number between 0 and 1.
|
|
102
|
-
* Note: This is NOT cryptographically secure. For secure random values,
|
|
103
|
-
* use crypto.getRandomValues().
|
|
104
|
-
*/
|
|
105
25
|
const random = _Math.random;
|
|
106
26
|
|
|
107
|
-
/**
|
|
108
|
-
* Generates a version 4 UUID.
|
|
109
|
-
*
|
|
110
|
-
* @returns a version 4 UUID.
|
|
111
|
-
*
|
|
112
|
-
* @example Creating unique identifiers for entities
|
|
113
|
-
* ```typescript
|
|
114
|
-
* const userId = uuidV4()
|
|
115
|
-
* // => 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d'
|
|
116
|
-
*
|
|
117
|
-
* const sessionId = uuidV4()
|
|
118
|
-
* // => '9f8e7d6c-5b4a-4321-8765-4321fedcba98'
|
|
119
|
-
* ```
|
|
120
|
-
*/
|
|
121
27
|
function uuidV4() {
|
|
122
28
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
|
|
123
29
|
const randomHex = (random() * 16) | 0;
|
|
@@ -126,17 +32,7 @@
|
|
|
126
32
|
});
|
|
127
33
|
}
|
|
128
34
|
|
|
129
|
-
/**
|
|
130
|
-
* Protocol prefix for all action types.
|
|
131
|
-
* This is the Nexus protocol identifier.
|
|
132
|
-
*/
|
|
133
35
|
const PROTOCOL = 'nexus';
|
|
134
|
-
/**
|
|
135
|
-
* Action type string literals for the Nexus protocol.
|
|
136
|
-
*
|
|
137
|
-
* These define the wire format for all connection lifecycle actions.
|
|
138
|
-
* The connection flow behavior is 1:1 with the proven legacy implementation.
|
|
139
|
-
*/
|
|
140
36
|
const ACTION_TYPES = {
|
|
141
37
|
INVALID_REQUEST: `[${PROTOCOL}] invalid-request`,
|
|
142
38
|
REQUEST_CONNECTION: `[${PROTOCOL}] connection-request`,
|
|
@@ -150,66 +46,10 @@
|
|
|
150
46
|
OPEN_CONNECTION: `[${PROTOCOL}] connection-opened`,
|
|
151
47
|
NEW_MESSAGE: `[${PROTOCOL}] new-message`,
|
|
152
48
|
};
|
|
153
|
-
/**
|
|
154
|
-
* Type guards for specific action types
|
|
155
|
-
*
|
|
156
|
-
* @param action - The action to check
|
|
157
|
-
* @returns True if action contains a contract property
|
|
158
|
-
*
|
|
159
|
-
* @example Checking for contract property
|
|
160
|
-
* ```typescript
|
|
161
|
-
* if (isActionWithContract(action)) {
|
|
162
|
-
* console.log(action.contract)
|
|
163
|
-
* }
|
|
164
|
-
* ```
|
|
165
|
-
*/
|
|
166
49
|
const isActionWithContract = (action) => 'contract' in action;
|
|
167
|
-
/**
|
|
168
|
-
* Type guard for actions with data property.
|
|
169
|
-
*
|
|
170
|
-
* @param action - The action to check
|
|
171
|
-
* @returns True if action contains a data property
|
|
172
|
-
*
|
|
173
|
-
* @example Checking for data property
|
|
174
|
-
* ```typescript
|
|
175
|
-
* if (isActionWithData(action)) {
|
|
176
|
-
* processData(action.data)
|
|
177
|
-
* }
|
|
178
|
-
* ```
|
|
179
|
-
*/
|
|
180
50
|
const isActionWithData = (action) => 'data' in action;
|
|
181
|
-
/**
|
|
182
|
-
* Type guard for actions with processId property.
|
|
183
|
-
*
|
|
184
|
-
* @param action - The action to check
|
|
185
|
-
* @returns True if action contains a processId property
|
|
186
|
-
*
|
|
187
|
-
* @example Checking for processId property
|
|
188
|
-
* ```typescript
|
|
189
|
-
* if (isActionWithProcess(action)) {
|
|
190
|
-
* trackProcess(action.processId)
|
|
191
|
-
* }
|
|
192
|
-
* ```
|
|
193
|
-
*/
|
|
194
51
|
const isActionWithProcess = (action) => 'processId' in action;
|
|
195
52
|
|
|
196
|
-
/**
|
|
197
|
-
* Creates ACCEPT_CONNECTION action
|
|
198
|
-
*
|
|
199
|
-
* @param deps - Action dependencies (getBrokerId, getContract)
|
|
200
|
-
* @returns Function that takes processId and optional security response, returns frozen action
|
|
201
|
-
*
|
|
202
|
-
* @example Creating accept connection actions
|
|
203
|
-
* ```typescript
|
|
204
|
-
* // Without security
|
|
205
|
-
* const action = acceptConnection(deps)('process-123')
|
|
206
|
-
*
|
|
207
|
-
* // With security negotiation response
|
|
208
|
-
* const secureAction = acceptConnection(deps)('process-123', {
|
|
209
|
-
* negotiated: 'v2'
|
|
210
|
-
* })
|
|
211
|
-
* ```
|
|
212
|
-
*/
|
|
213
53
|
const acceptConnection = (deps) => (processId, security) => {
|
|
214
54
|
const base = {
|
|
215
55
|
type: ACTION_TYPES.ACCEPT_CONNECTION,
|
|
@@ -223,57 +63,18 @@
|
|
|
223
63
|
return freeze(base);
|
|
224
64
|
};
|
|
225
65
|
|
|
226
|
-
/**
|
|
227
|
-
* Creates a cancel connection action.
|
|
228
|
-
*
|
|
229
|
-
* @param deps - Action dependencies containing broker ID
|
|
230
|
-
* @returns A function that creates a cancel connection action for a process
|
|
231
|
-
*
|
|
232
|
-
* @example Creating cancel connection actions
|
|
233
|
-
* ```typescript
|
|
234
|
-
* const createCancelAction = cancelConnection({ getBrokerId: () => 'broker-1' })
|
|
235
|
-
* const action = createCancelAction('process-123')
|
|
236
|
-
* // => { type: 'CANCEL_CONNECTION', processId: 'process-123', senderId: 'broker-1' }
|
|
237
|
-
* ```
|
|
238
|
-
*/
|
|
239
66
|
const cancelConnection = (deps) => (processId) => freeze({
|
|
240
67
|
type: ACTION_TYPES.CANCEL_CONNECTION,
|
|
241
68
|
processId,
|
|
242
69
|
senderId: deps.getBrokerId(),
|
|
243
70
|
});
|
|
244
71
|
|
|
245
|
-
/**
|
|
246
|
-
* Creates a close connection action.
|
|
247
|
-
*
|
|
248
|
-
* @param deps - Action dependencies containing broker ID
|
|
249
|
-
* @returns A function that creates a close connection action for a process
|
|
250
|
-
*
|
|
251
|
-
* @example Creating close connection actions
|
|
252
|
-
* ```typescript
|
|
253
|
-
* const createCloseAction = closeConnection({ getBrokerId: () => 'broker-1' })
|
|
254
|
-
* const action = createCloseAction('process-123')
|
|
255
|
-
* // => { type: 'CLOSE_CONNECTION', processId: 'process-123', senderId: 'broker-1' }
|
|
256
|
-
* ```
|
|
257
|
-
*/
|
|
258
72
|
const closeConnection = (deps) => (processId) => freeze({
|
|
259
73
|
type: ACTION_TYPES.CLOSE_CONNECTION,
|
|
260
74
|
processId,
|
|
261
75
|
senderId: deps.getBrokerId(),
|
|
262
76
|
});
|
|
263
77
|
|
|
264
|
-
/**
|
|
265
|
-
* Creates a deny connection action with an error message.
|
|
266
|
-
*
|
|
267
|
-
* @param deps - Action dependencies containing broker ID
|
|
268
|
-
* @returns A function that creates a deny connection action for a process
|
|
269
|
-
*
|
|
270
|
-
* @example Creating deny connection actions
|
|
271
|
-
* ```typescript
|
|
272
|
-
* const createDenyAction = denyConnection({ getBrokerId: () => 'broker-1' })
|
|
273
|
-
* const action = createDenyAction('process-123', 'Origin not allowed')
|
|
274
|
-
* // => { type: 'DENY_CONNECTION', processId: 'process-123', senderId: 'broker-1', error: 'Origin not allowed' }
|
|
275
|
-
* ```
|
|
276
|
-
*/
|
|
277
78
|
const denyConnection = (deps) => (processId, error) => freeze({
|
|
278
79
|
type: ACTION_TYPES.DENY_CONNECTION,
|
|
279
80
|
processId,
|
|
@@ -281,37 +82,11 @@
|
|
|
281
82
|
error,
|
|
282
83
|
});
|
|
283
84
|
|
|
284
|
-
/**
|
|
285
|
-
* Creates a destroy connection action.
|
|
286
|
-
*
|
|
287
|
-
* @param deps - Action dependencies containing broker ID
|
|
288
|
-
* @returns A function that creates a destroy connection action
|
|
289
|
-
*
|
|
290
|
-
* @example Creating destroy connection actions
|
|
291
|
-
* ```typescript
|
|
292
|
-
* const createDestroyAction = destroyConnection({ getBrokerId: () => 'broker-1' })
|
|
293
|
-
* const action = createDestroyAction()
|
|
294
|
-
* // => { type: 'DESTROY_CONNECTION', senderId: 'broker-1' }
|
|
295
|
-
* ```
|
|
296
|
-
*/
|
|
297
85
|
const destroyConnection = (deps) => () => freeze({
|
|
298
86
|
type: ACTION_TYPES.DESTROY_CONNECTION,
|
|
299
87
|
senderId: deps.getBrokerId(),
|
|
300
88
|
});
|
|
301
89
|
|
|
302
|
-
/**
|
|
303
|
-
* Creates an invalid request action with an error message.
|
|
304
|
-
*
|
|
305
|
-
* @param deps - Action dependencies containing broker ID
|
|
306
|
-
* @returns A function that creates an invalid request action for a process
|
|
307
|
-
*
|
|
308
|
-
* @example Creating invalid request actions
|
|
309
|
-
* ```typescript
|
|
310
|
-
* const createInvalidAction = invalidRequest({ getBrokerId: () => 'broker-1' })
|
|
311
|
-
* const action = createInvalidAction('process-123', 'Malformed payload')
|
|
312
|
-
* // => { type: 'INVALID_REQUEST', processId: 'process-123', senderId: 'broker-1', error: 'Malformed payload' }
|
|
313
|
-
* ```
|
|
314
|
-
*/
|
|
315
90
|
const invalidRequest = (deps) => (processId, error) => freeze({
|
|
316
91
|
type: ACTION_TYPES.INVALID_REQUEST,
|
|
317
92
|
processId,
|
|
@@ -319,43 +94,12 @@
|
|
|
319
94
|
error,
|
|
320
95
|
});
|
|
321
96
|
|
|
322
|
-
|
|
323
|
-
* Creates a new message action with data payload.
|
|
324
|
-
*
|
|
325
|
-
* @param deps - Action dependencies containing broker ID
|
|
326
|
-
* @returns A function that creates a new message action with data
|
|
327
|
-
*
|
|
328
|
-
* @example Creating message actions with data
|
|
329
|
-
* ```typescript
|
|
330
|
-
* const createMessageAction = newMessage({ getBrokerId: () => 'broker-1' })
|
|
331
|
-
* const action = createMessageAction({ userId: 123, event: 'login' })
|
|
332
|
-
* // => { type: 'NEW_MESSAGE', senderId: 'broker-1', data: { userId: 123, event: 'login' } }
|
|
333
|
-
* ```
|
|
334
|
-
*/
|
|
335
|
-
const newMessage = (deps) => (data) => freeze({
|
|
97
|
+
const newMessage = (deps) => (message) => freeze({
|
|
336
98
|
type: ACTION_TYPES.NEW_MESSAGE,
|
|
337
99
|
senderId: deps.getBrokerId(),
|
|
338
|
-
data,
|
|
100
|
+
data: message,
|
|
339
101
|
});
|
|
340
102
|
|
|
341
|
-
/**
|
|
342
|
-
* Creates OPEN_CONNECTION action
|
|
343
|
-
*
|
|
344
|
-
* @param deps - Action dependencies (getBrokerId, getContract)
|
|
345
|
-
* @returns Function that takes processId and optional security confirmation, returns frozen action
|
|
346
|
-
*
|
|
347
|
-
* @example Creating open connection actions
|
|
348
|
-
* ```typescript
|
|
349
|
-
* // Without security
|
|
350
|
-
* const action = openConnection(deps)('process-123')
|
|
351
|
-
*
|
|
352
|
-
* // With security confirmation
|
|
353
|
-
* const secureAction = openConnection(deps)('process-123', {
|
|
354
|
-
* active: true,
|
|
355
|
-
* protocol: 'v2'
|
|
356
|
-
* })
|
|
357
|
-
* ```
|
|
358
|
-
*/
|
|
359
103
|
const openConnection = (deps) => (processId, security) => {
|
|
360
104
|
const base = {
|
|
361
105
|
type: ACTION_TYPES.OPEN_CONNECTION,
|
|
@@ -368,24 +112,6 @@
|
|
|
368
112
|
return freeze(base);
|
|
369
113
|
};
|
|
370
114
|
|
|
371
|
-
/**
|
|
372
|
-
* Creates REQUEST_CONNECTION action
|
|
373
|
-
*
|
|
374
|
-
* @param deps - Action dependencies (getBrokerId, getContract)
|
|
375
|
-
* @returns Function that takes processId and optional security request, returns frozen action
|
|
376
|
-
*
|
|
377
|
-
* @example Creating request connection actions
|
|
378
|
-
* ```typescript
|
|
379
|
-
* // Without security
|
|
380
|
-
* const action = requestConnection(deps)('process-123')
|
|
381
|
-
*
|
|
382
|
-
* // With security negotiation
|
|
383
|
-
* const secureAction = requestConnection(deps)('process-123', {
|
|
384
|
-
* supported: ['v2', 'v1', 'none'],
|
|
385
|
-
* preferred: 'v2'
|
|
386
|
-
* })
|
|
387
|
-
* ```
|
|
388
|
-
*/
|
|
389
115
|
const requestConnection = (deps) => (processId, security) => {
|
|
390
116
|
const base = {
|
|
391
117
|
type: ACTION_TYPES.REQUEST_CONNECTION,
|
|
@@ -399,21 +125,6 @@
|
|
|
399
125
|
return freeze(base);
|
|
400
126
|
};
|
|
401
127
|
|
|
402
|
-
/**
|
|
403
|
-
* Creates all action creators bound to the provided dependencies
|
|
404
|
-
*
|
|
405
|
-
* @param deps - Action dependencies (getBrokerId, getContract)
|
|
406
|
-
* @returns Frozen object containing all action creator functions
|
|
407
|
-
*
|
|
408
|
-
* @example Creating action creators
|
|
409
|
-
* ```typescript
|
|
410
|
-
* const actions = createActionCreators({
|
|
411
|
-
* getBrokerId: () => 'broker-123',
|
|
412
|
-
* getContract: () => myContract
|
|
413
|
-
* })
|
|
414
|
-
* const action = actions.requestConnection('process-456')
|
|
415
|
-
* ```
|
|
416
|
-
*/
|
|
417
128
|
const createActionCreators = (deps) => freeze({
|
|
418
129
|
requestConnection: requestConnection(deps),
|
|
419
130
|
acceptConnection: acceptConnection(deps),
|
|
@@ -426,102 +137,26 @@
|
|
|
426
137
|
invalidRequest: invalidRequest(deps),
|
|
427
138
|
});
|
|
428
139
|
|
|
429
|
-
/**
|
|
430
|
-
* Safe Map factory with optional groupBy for ES2024+.
|
|
431
|
-
*
|
|
432
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/map
|
|
433
|
-
*/
|
|
434
|
-
/* eslint-disable workspace/lib-require-jsdoc-example */
|
|
435
140
|
const _Map = globalThis.Map;
|
|
436
|
-
const _Reflect$
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
* Use this instead of `new Map()`.
|
|
440
|
-
*
|
|
441
|
-
* @param iterable - Optional iterable of key-value pairs.
|
|
442
|
-
* @returns A new Map instance.
|
|
443
|
-
*/
|
|
444
|
-
const createMap = (iterable) => _Reflect$7.construct(_Map, iterable ? [iterable] : []);
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* Clears all processes from the registry
|
|
448
|
-
*
|
|
449
|
-
* @param processes - Map storing process to channel mappings
|
|
450
|
-
* @returns Function that clears all processes
|
|
451
|
-
*
|
|
452
|
-
* @example Clearing all processes
|
|
453
|
-
* ```typescript
|
|
454
|
-
* const clear = clearProcesses(processMap)
|
|
455
|
-
* clear() // All processes removed
|
|
456
|
-
* ```
|
|
457
|
-
*/
|
|
141
|
+
const _Reflect$8 = globalThis.Reflect;
|
|
142
|
+
const createMap = (iterable) => _Reflect$8.construct(_Map, iterable ? [iterable] : []);
|
|
143
|
+
|
|
458
144
|
const clearProcesses = (processes) => () => {
|
|
459
145
|
processes.clear();
|
|
460
146
|
};
|
|
461
147
|
|
|
462
|
-
/**
|
|
463
|
-
* Creates a process ID and registers the channel
|
|
464
|
-
*
|
|
465
|
-
* @param processes - Map storing process to channel mappings
|
|
466
|
-
* @returns Function that takes a channel and returns new process ID
|
|
467
|
-
*
|
|
468
|
-
* @example Registering a channel process
|
|
469
|
-
* ```typescript
|
|
470
|
-
* const create = createProcess(processMap)
|
|
471
|
-
* const processId = create(myChannel)
|
|
472
|
-
* ```
|
|
473
|
-
*/
|
|
474
148
|
const createProcess = (processes) => (channel) => {
|
|
475
149
|
const processId = uuidV4();
|
|
476
150
|
processes.set(processId, channel);
|
|
477
151
|
return processId;
|
|
478
152
|
};
|
|
479
153
|
|
|
480
|
-
/**
|
|
481
|
-
* Gets a channel by its process ID
|
|
482
|
-
*
|
|
483
|
-
* @param processes - Map storing process to channel mappings
|
|
484
|
-
* @returns Function that takes processId and returns channel or undefined
|
|
485
|
-
*
|
|
486
|
-
* @example Looking up channel by process ID
|
|
487
|
-
* ```typescript
|
|
488
|
-
* const processes = new Map([['proc-1', channelHandle]])
|
|
489
|
-
* const findChannel = getChannel(processes)
|
|
490
|
-
* const channel = findChannel('proc-1')
|
|
491
|
-
* // => channelHandle or undefined
|
|
492
|
-
* ```
|
|
493
|
-
*/
|
|
494
154
|
const getChannel$1 = (processes) => (processId) => processes.get(processId);
|
|
495
155
|
|
|
496
|
-
/**
|
|
497
|
-
* Removes a process ID from the registry
|
|
498
|
-
*
|
|
499
|
-
* @param processes - Map storing process to channel mappings
|
|
500
|
-
* @returns Function that takes processId and removes it
|
|
501
|
-
*
|
|
502
|
-
* @example Removing a process
|
|
503
|
-
* ```typescript
|
|
504
|
-
* const remove = removeProcess(processMap)
|
|
505
|
-
* remove('some-process-id')
|
|
506
|
-
* ```
|
|
507
|
-
*/
|
|
508
156
|
const removeProcess = (processes) => (processId) => {
|
|
509
157
|
processes.delete(processId);
|
|
510
158
|
};
|
|
511
159
|
|
|
512
|
-
/**
|
|
513
|
-
* Creates a process manager for tracking process IDs to channel mappings
|
|
514
|
-
*
|
|
515
|
-
* @returns Object with process management methods
|
|
516
|
-
*
|
|
517
|
-
* @example Managing channel processes
|
|
518
|
-
* ```typescript
|
|
519
|
-
* const processManager = createProcessManager()
|
|
520
|
-
* const processId = processManager.create(channel)
|
|
521
|
-
* const found = processManager.get(processId)
|
|
522
|
-
* processManager.remove(processId)
|
|
523
|
-
* ```
|
|
524
|
-
*/
|
|
525
160
|
const createProcessManager = () => {
|
|
526
161
|
const processes = createMap();
|
|
527
162
|
return freeze({
|
|
@@ -529,98 +164,27 @@
|
|
|
529
164
|
get: getChannel$1(processes),
|
|
530
165
|
remove: removeProcess(processes),
|
|
531
166
|
clear: clearProcesses(processes),
|
|
532
|
-
/**
|
|
533
|
-
* Track an existing process ID with a channel.
|
|
534
|
-
* Useful when receiving a process ID from the remote side that needs to be associated
|
|
535
|
-
* with a local channel instance.
|
|
536
|
-
*
|
|
537
|
-
* @param processId - The process ID to track
|
|
538
|
-
* @param channel - The channel handle to associate with the process ID
|
|
539
|
-
*/
|
|
540
167
|
track: (processId, channel) => {
|
|
541
168
|
processes.set(processId, channel);
|
|
542
169
|
},
|
|
543
|
-
/**
|
|
544
|
-
* Check if a process ID exists in the manager.
|
|
545
|
-
*
|
|
546
|
-
* @param processId - The process ID to check
|
|
547
|
-
* @returns True if the process ID exists, false otherwise
|
|
548
|
-
*/
|
|
549
170
|
has: (processId) => {
|
|
550
171
|
return processes.has(processId);
|
|
551
172
|
},
|
|
552
173
|
});
|
|
553
174
|
};
|
|
554
175
|
|
|
555
|
-
/**
|
|
556
|
-
* Safe copies of Array built-in static methods.
|
|
557
|
-
*
|
|
558
|
-
* These references are captured at module initialization time to protect against
|
|
559
|
-
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
560
|
-
*
|
|
561
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/array
|
|
562
|
-
*/
|
|
563
176
|
const _Array = globalThis.Array;
|
|
564
|
-
/**
|
|
565
|
-
* (Safe copy) Determines whether the passed value is an Array.
|
|
566
|
-
*/
|
|
567
177
|
const isArray = _Array.isArray;
|
|
568
|
-
/**
|
|
569
|
-
* (Safe copy) Creates an array from an array-like or iterable object.
|
|
570
|
-
*/
|
|
571
178
|
const from = _Array.from;
|
|
572
179
|
|
|
573
|
-
/**
|
|
574
|
-
* Safe Set factory for protected set construction.
|
|
575
|
-
*
|
|
576
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/set
|
|
577
|
-
*/
|
|
578
|
-
/* eslint-disable workspace/lib-require-jsdoc-example */
|
|
579
180
|
const _Set = globalThis.Set;
|
|
580
|
-
const _Reflect$
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
* Use this instead of `new Set()`.
|
|
584
|
-
*
|
|
585
|
-
* @param iterable - Optional iterable of values.
|
|
586
|
-
* @returns A new Set instance.
|
|
587
|
-
*/
|
|
588
|
-
const createSet = (iterable) => _Reflect$6.construct(_Set, iterable ? [iterable] : []);
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
* Safe WeakMap factory for protected weak map construction.
|
|
592
|
-
*
|
|
593
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/weak-map
|
|
594
|
-
*/
|
|
595
|
-
/* eslint-disable workspace/lib-require-jsdoc-example */
|
|
181
|
+
const _Reflect$7 = globalThis.Reflect;
|
|
182
|
+
const createSet = (iterable) => _Reflect$7.construct(_Set, iterable ? [iterable] : []);
|
|
183
|
+
|
|
596
184
|
const _WeakMap = globalThis.WeakMap;
|
|
597
|
-
const _Reflect$
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
* Use this instead of `new WeakMap()`.
|
|
601
|
-
*
|
|
602
|
-
* @param iterable - Optional iterable of key-value pairs.
|
|
603
|
-
* @returns A new WeakMap instance.
|
|
604
|
-
*/
|
|
605
|
-
const createWeakMap = (iterable) => _Reflect$5.construct(_WeakMap, iterable ? [iterable] : []);
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* Minimal channel structure required for registry operations.
|
|
609
|
-
* Extended with optional methods for use with full ChannelHandle objects.
|
|
610
|
-
*/
|
|
611
|
-
/**
|
|
612
|
-
* Creates a new channel registry with isolated state.
|
|
613
|
-
* All lookup operations are O(1) using WeakMap/Map.
|
|
614
|
-
*
|
|
615
|
-
* @returns Registry functions for managing channels
|
|
616
|
-
*
|
|
617
|
-
* @example Creating and using a registry
|
|
618
|
-
* ```typescript
|
|
619
|
-
* const registry = createRegistry()
|
|
620
|
-
* registry.add({ id: 'ch-1', name: 'main', target: iframe.contentWindow })
|
|
621
|
-
* const channel = registry.getById('ch-1')
|
|
622
|
-
* ```
|
|
623
|
-
*/
|
|
185
|
+
const _Reflect$6 = globalThis.Reflect;
|
|
186
|
+
const createWeakMap = (iterable) => _Reflect$6.construct(_WeakMap, iterable ? [iterable] : []);
|
|
187
|
+
|
|
624
188
|
function createRegistry() {
|
|
625
189
|
const windowMap = createWeakMap();
|
|
626
190
|
const idMap = createMap();
|
|
@@ -667,31 +231,10 @@
|
|
|
667
231
|
});
|
|
668
232
|
}
|
|
669
233
|
|
|
670
|
-
/* eslint-disable workspace/lib-require-jsdoc-example */
|
|
671
|
-
/**
|
|
672
|
-
* Check if value is an object (not null, not array)
|
|
673
|
-
*
|
|
674
|
-
* @param value - The value to check
|
|
675
|
-
* @returns True if value is a plain object
|
|
676
|
-
*/
|
|
677
234
|
function isObject(value) {
|
|
678
235
|
return value !== null && typeof value === 'object' && !isArray(value);
|
|
679
236
|
}
|
|
680
237
|
|
|
681
|
-
/**
|
|
682
|
-
* Validates a channel contract structure.
|
|
683
|
-
*
|
|
684
|
-
* @param contract - The contract to validate
|
|
685
|
-
* @throws {Error} Error if contract is invalid
|
|
686
|
-
*
|
|
687
|
-
* @example Validating contract structure
|
|
688
|
-
* ```typescript
|
|
689
|
-
* validateContract({
|
|
690
|
-
* emitted: [{ type: 'ping' }],
|
|
691
|
-
* accepted: [{ type: 'pong' }]
|
|
692
|
-
* })
|
|
693
|
-
* ```
|
|
694
|
-
*/
|
|
695
238
|
function validateContract$1(contract) {
|
|
696
239
|
if (!contract) {
|
|
697
240
|
throw createError('Contract cannot be null or undefined');
|
|
@@ -725,27 +268,9 @@
|
|
|
725
268
|
}
|
|
726
269
|
}
|
|
727
270
|
|
|
728
|
-
/**
|
|
729
|
-
* Helper to check if a string is empty after trimming
|
|
730
|
-
*
|
|
731
|
-
* @param str - The string to check
|
|
732
|
-
* @returns True if string is empty after trimming
|
|
733
|
-
*/
|
|
734
271
|
function isEmpty(str) {
|
|
735
272
|
return str.trim().length === 0;
|
|
736
273
|
}
|
|
737
|
-
/**
|
|
738
|
-
* Validates a channel or broker name.
|
|
739
|
-
*
|
|
740
|
-
* @param name - The name to validate
|
|
741
|
-
* @throws {Error} Error if name is invalid
|
|
742
|
-
*
|
|
743
|
-
* @example Validating channel name
|
|
744
|
-
* ```typescript
|
|
745
|
-
* validateName('my-channel') // valid
|
|
746
|
-
* validateName('') // throws Error
|
|
747
|
-
* ```
|
|
748
|
-
*/
|
|
749
274
|
function validateName(name) {
|
|
750
275
|
if (name === null || name === undefined) {
|
|
751
276
|
throw createError('Name cannot be null or undefined');
|
|
@@ -758,98 +283,29 @@
|
|
|
758
283
|
}
|
|
759
284
|
}
|
|
760
285
|
|
|
761
|
-
/**
|
|
762
|
-
* Protocol registry factory.
|
|
763
|
-
*
|
|
764
|
-
* Creates a registry for managing protocol providers at the broker level.
|
|
765
|
-
*
|
|
766
|
-
* @module security/registry/factory
|
|
767
|
-
*/
|
|
768
|
-
/**
|
|
769
|
-
* Creates a protocol registry for managing security protocol providers.
|
|
770
|
-
*
|
|
771
|
-
* The registry provides a centralized store for protocol providers,
|
|
772
|
-
* allowing channels to retrieve the appropriate provider based on
|
|
773
|
-
* the negotiated protocol version.
|
|
774
|
-
*
|
|
775
|
-
* The 'none' protocol is always considered supported (it requires
|
|
776
|
-
* no provider) and cannot be registered or unregistered.
|
|
777
|
-
*
|
|
778
|
-
* @returns A new protocol registry instance
|
|
779
|
-
*
|
|
780
|
-
* @example Managing protocol providers
|
|
781
|
-
* ```typescript
|
|
782
|
-
* const registry = createProtocolRegistry()
|
|
783
|
-
*
|
|
784
|
-
* // Register v1 protocol
|
|
785
|
-
* registry.register('v1', createProtocol(logger, 60))
|
|
786
|
-
*
|
|
787
|
-
* // Check availability
|
|
788
|
-
* registry.has('v1') // true
|
|
789
|
-
* registry.has('v2') // false
|
|
790
|
-
* registry.has('none') // always true
|
|
791
|
-
*
|
|
792
|
-
* // Get supported versions
|
|
793
|
-
* registry.getSupportedVersions() // ['v1', 'none']
|
|
794
|
-
* ```
|
|
795
|
-
*/
|
|
796
286
|
function createProtocolRegistry() {
|
|
797
287
|
const providers = createMap();
|
|
798
|
-
/**
|
|
799
|
-
* Register a protocol provider.
|
|
800
|
-
*
|
|
801
|
-
* @param version - The protocol version ('v1' or 'v2')
|
|
802
|
-
* @param provider - The protocol provider instance
|
|
803
|
-
*/
|
|
804
288
|
const register = (version, provider) => {
|
|
805
289
|
if (!provider) {
|
|
806
290
|
throw createError(`Cannot register null/undefined provider for ${version}`);
|
|
807
291
|
}
|
|
808
292
|
providers.set(version, provider);
|
|
809
293
|
};
|
|
810
|
-
/**
|
|
811
|
-
* Unregister a protocol provider.
|
|
812
|
-
*
|
|
813
|
-
* @param version - The protocol version to unregister
|
|
814
|
-
*/
|
|
815
294
|
const unregister = (version) => {
|
|
816
295
|
providers.delete(version);
|
|
817
296
|
};
|
|
818
|
-
/**
|
|
819
|
-
* Get a registered protocol provider.
|
|
820
|
-
*
|
|
821
|
-
* Returns undefined for 'none' since it requires no provider.
|
|
822
|
-
*
|
|
823
|
-
* @param version - The protocol version to retrieve
|
|
824
|
-
* @returns The provider if registered, otherwise undefined
|
|
825
|
-
*/
|
|
826
297
|
const get = (version) => {
|
|
827
298
|
if (version === 'none') {
|
|
828
299
|
return undefined;
|
|
829
300
|
}
|
|
830
301
|
return providers.get(version);
|
|
831
302
|
};
|
|
832
|
-
/**
|
|
833
|
-
* Check if a protocol provider is registered.
|
|
834
|
-
*
|
|
835
|
-
* The 'none' protocol is always considered available.
|
|
836
|
-
*
|
|
837
|
-
* @param version - The protocol version to check
|
|
838
|
-
* @returns True if the provider is registered (or 'none')
|
|
839
|
-
*/
|
|
840
303
|
const has = (version) => {
|
|
841
304
|
if (version === 'none') {
|
|
842
305
|
return true;
|
|
843
306
|
}
|
|
844
307
|
return providers.has(version);
|
|
845
308
|
};
|
|
846
|
-
/**
|
|
847
|
-
* Get all supported protocol versions.
|
|
848
|
-
*
|
|
849
|
-
* Returns versions that have registered providers plus 'none'.
|
|
850
|
-
*
|
|
851
|
-
* @returns Array of supported protocol versions
|
|
852
|
-
*/
|
|
853
309
|
const getSupportedVersions = () => {
|
|
854
310
|
const versions = ['none'];
|
|
855
311
|
if (providers.has('v1')) {
|
|
@@ -869,20 +325,6 @@
|
|
|
869
325
|
});
|
|
870
326
|
}
|
|
871
327
|
|
|
872
|
-
/**
|
|
873
|
-
* Merges multiple channel contracts into a single contract
|
|
874
|
-
*
|
|
875
|
-
* @param contracts - The contracts to merge
|
|
876
|
-
* @returns A single merged contract containing all accepted and provided actions
|
|
877
|
-
*
|
|
878
|
-
* @example Merging channel contracts
|
|
879
|
-
* ```typescript
|
|
880
|
-
* const contract1 = { accepted: [{ type: 'a' }], provided: [{ type: 'b' }] }
|
|
881
|
-
* const contract2 = { accepted: [{ type: 'c' }], provided: [{ type: 'd' }] }
|
|
882
|
-
* const merged = mergeContracts(contract1, contract2)
|
|
883
|
-
* // merged = { accepted: [{ type: 'a' }, { type: 'c' }], emitted: [{ type: 'b' }, { type: 'd' }] }
|
|
884
|
-
* ```
|
|
885
|
-
*/
|
|
886
328
|
function mergeContracts(...contracts) {
|
|
887
329
|
const mergedContract = {
|
|
888
330
|
accepted: [],
|
|
@@ -919,38 +361,9 @@
|
|
|
919
361
|
info: 1,
|
|
920
362
|
debug: 0,
|
|
921
363
|
};
|
|
922
|
-
/**
|
|
923
|
-
* Validates whether a given string is a valid log level.
|
|
924
|
-
*
|
|
925
|
-
* @param level - The log level to validate
|
|
926
|
-
* @returns True if the level is valid, false otherwise
|
|
927
|
-
*
|
|
928
|
-
* @example Validating log levels
|
|
929
|
-
* ```typescript
|
|
930
|
-
* isValidLogLevel('error') // => true
|
|
931
|
-
* isValidLogLevel('verbose') // => false
|
|
932
|
-
* ```
|
|
933
|
-
*/
|
|
934
364
|
function isValidLogLevel(level) {
|
|
935
365
|
return logLevels.includes(level);
|
|
936
366
|
}
|
|
937
|
-
/**
|
|
938
|
-
* Creates a log level configuration manager for controlling logging behavior.
|
|
939
|
-
* Provides methods to get, set, and evaluate log levels based on priority.
|
|
940
|
-
*
|
|
941
|
-
* @param level - The initial log level (defaults to 'error')
|
|
942
|
-
* @returns A configuration object with log level management methods
|
|
943
|
-
* @throws {Error} When the provided level is not a valid log level
|
|
944
|
-
*
|
|
945
|
-
* @example Managing log levels with priority checks
|
|
946
|
-
* ```typescript
|
|
947
|
-
* const config = createLogLevelConfig('warn')
|
|
948
|
-
* config.shouldLog('error') // => true (error >= warn)
|
|
949
|
-
* config.shouldLog('debug') // => false (debug < warn)
|
|
950
|
-
* config.setLogLevel('debug')
|
|
951
|
-
* config.shouldLog('debug') // => true
|
|
952
|
-
* ```
|
|
953
|
-
*/
|
|
954
367
|
function createLogLevelConfig(level = 'error') {
|
|
955
368
|
if (!isValidLogLevel(level)) {
|
|
956
369
|
throw createError('Cannot create log level configuration with a valid default log level');
|
|
@@ -1002,18 +415,6 @@
|
|
|
1002
415
|
},
|
|
1003
416
|
];
|
|
1004
417
|
|
|
1005
|
-
/**
|
|
1006
|
-
* Gets the keys from an iterable target based on its data type.
|
|
1007
|
-
*
|
|
1008
|
-
* @param target - The target to get the keys from.
|
|
1009
|
-
* @param dataType - The data type of the target.
|
|
1010
|
-
* @returns The keys from the iterable target.
|
|
1011
|
-
*
|
|
1012
|
-
* @example Extracting keys from object
|
|
1013
|
-
* ```typescript
|
|
1014
|
-
* getKeysFromIterable({ a: 1, b: 2 }, 'object') // ['a', 'b']
|
|
1015
|
-
* ```
|
|
1016
|
-
*/
|
|
1017
418
|
const getKeysFromIterable = (target, dataType) => {
|
|
1018
419
|
if (dataType === 'array')
|
|
1019
420
|
dataType = Array.name;
|
|
@@ -1025,21 +426,6 @@
|
|
|
1025
426
|
return iterableClass.getKeys(target);
|
|
1026
427
|
};
|
|
1027
428
|
|
|
1028
|
-
/**
|
|
1029
|
-
* Returns the data type of the target.
|
|
1030
|
-
* Uses native `typeof` operator, however, makes distinction between `null`, `array`, and `object`.
|
|
1031
|
-
* Also, when classes are registered via `registerClass`, it checks if objects are instance of any known registered class.
|
|
1032
|
-
*
|
|
1033
|
-
* @param target - The target to get the data type of.
|
|
1034
|
-
* @returns The data type of the target.
|
|
1035
|
-
*
|
|
1036
|
-
* @example Determining data types
|
|
1037
|
-
* ```typescript
|
|
1038
|
-
* getType([1, 2]) // 'array'
|
|
1039
|
-
* getType({ a: 1 }) // 'object'
|
|
1040
|
-
* getType(null) // 'null'
|
|
1041
|
-
* ```
|
|
1042
|
-
*/
|
|
1043
429
|
const getType = (target) => {
|
|
1044
430
|
if (target === null)
|
|
1045
431
|
return 'null';
|
|
@@ -1055,17 +441,6 @@
|
|
|
1055
441
|
return nativeDataType;
|
|
1056
442
|
};
|
|
1057
443
|
|
|
1058
|
-
/**
|
|
1059
|
-
* Returns a list of iterable data types. By default 'array' and 'object' are included.,
|
|
1060
|
-
* but can be extended by using `registerIterableClass`.
|
|
1061
|
-
*
|
|
1062
|
-
* @returns Array of iterable data types.
|
|
1063
|
-
*
|
|
1064
|
-
* @example Listing registered iterable types
|
|
1065
|
-
* ```typescript
|
|
1066
|
-
* getIterableTypes() // ['array', 'object', ...registered types]
|
|
1067
|
-
* ```
|
|
1068
|
-
*/
|
|
1069
444
|
const getIterableTypes = () => registeredIterableClasses.map(({ classRef }) => {
|
|
1070
445
|
const name = classRef.name;
|
|
1071
446
|
if (name === Object.name)
|
|
@@ -1075,109 +450,19 @@
|
|
|
1075
450
|
return name;
|
|
1076
451
|
});
|
|
1077
452
|
|
|
1078
|
-
/**
|
|
1079
|
-
* Checks if the provided data type is registered as an iterable type.
|
|
1080
|
-
*
|
|
1081
|
-
* @param dataType - The data type to check
|
|
1082
|
-
* @returns `true` if the data type is iterable, otherwise `false`
|
|
1083
|
-
*
|
|
1084
|
-
* @example Checking iterable types
|
|
1085
|
-
* ```typescript
|
|
1086
|
-
* isIterableType('array') // true
|
|
1087
|
-
* isIterableType('object') // true
|
|
1088
|
-
* isIterableType('string') // false
|
|
1089
|
-
* ```
|
|
1090
|
-
*/
|
|
1091
453
|
const isIterableType = (dataType) => getIterableTypes().includes(dataType);
|
|
1092
454
|
|
|
1093
|
-
/**
|
|
1094
|
-
* Checks if the target is iterable.
|
|
1095
|
-
*
|
|
1096
|
-
* @param target - The target to check.
|
|
1097
|
-
* @returns `true` if the target is iterable, `false` otherwise.
|
|
1098
|
-
*
|
|
1099
|
-
* @example Checking if value is iterable
|
|
1100
|
-
* ```typescript
|
|
1101
|
-
* isIterable([1, 2]) // true
|
|
1102
|
-
* isIterable({ a: 1 }) // true
|
|
1103
|
-
* isIterable('string') // false
|
|
1104
|
-
* ```
|
|
1105
|
-
*/
|
|
1106
455
|
const isIterable = (target) => isIterableType(getType(target));
|
|
1107
456
|
|
|
1108
|
-
/**
|
|
1109
|
-
* Safe copies of Date built-in via factory function and static methods.
|
|
1110
|
-
*
|
|
1111
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/date
|
|
1112
|
-
*/
|
|
1113
|
-
/* eslint-disable jsdoc/require-param */
|
|
1114
457
|
const _Date = globalThis.Date;
|
|
1115
|
-
const _Reflect$
|
|
1116
|
-
/**
|
|
1117
|
-
* (Safe copy) Creates a new Date using the captured Date constructor.
|
|
1118
|
-
* Use this instead of `new Date()`. Accepts all standard Date constructor signatures.
|
|
1119
|
-
*
|
|
1120
|
-
* @returns A new Date instance.
|
|
1121
|
-
*
|
|
1122
|
-
* @example Creating Date instances
|
|
1123
|
-
* ```typescript
|
|
1124
|
-
* const now = createDate()
|
|
1125
|
-
* const fromTimestamp = createDate(1704067200000)
|
|
1126
|
-
* const fromString = createDate('2024-01-01T00:00:00Z')
|
|
1127
|
-
* const fromParts = createDate(2024, 0, 1, 12, 30, 0) // Jan 1, 2024 12:30:00
|
|
1128
|
-
* ```
|
|
1129
|
-
*/
|
|
458
|
+
const _Reflect$5 = globalThis.Reflect;
|
|
1130
459
|
function createDate(...args) {
|
|
1131
|
-
return _Reflect$
|
|
1132
|
-
}
|
|
1133
|
-
/**
|
|
1134
|
-
* (Safe copy) Returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
|
1135
|
-
*
|
|
1136
|
-
* @example
|
|
1137
|
-
* ```typescript
|
|
1138
|
-
* const timestamp = dateNow()
|
|
1139
|
-
* // => 1704067200000 (example timestamp)
|
|
1140
|
-
* ```
|
|
1141
|
-
*/
|
|
460
|
+
return _Reflect$5.construct(_Date, args);
|
|
461
|
+
}
|
|
1142
462
|
const dateNow = _Date.now;
|
|
1143
|
-
/**
|
|
1144
|
-
* (Safe copy) Parses a string representation of a date.
|
|
1145
|
-
*
|
|
1146
|
-
* @example
|
|
1147
|
-
* ```typescript
|
|
1148
|
-
* const timestamp = dateParse('2024-01-01T00:00:00Z')
|
|
1149
|
-
* // => 1704067200000
|
|
1150
|
-
* ```
|
|
1151
|
-
*/
|
|
1152
463
|
const dateParse = _Date.parse;
|
|
1153
|
-
/**
|
|
1154
|
-
* (Safe copy) Returns the number of milliseconds in a Date object since January 1, 1970 UTC.
|
|
1155
|
-
*
|
|
1156
|
-
* @example
|
|
1157
|
-
* ```typescript
|
|
1158
|
-
* const timestamp = dateUTC(2024, 0, 1, 12, 0, 0)
|
|
1159
|
-
* // => 1704110400000 (Jan 1, 2024 12:00:00 UTC)
|
|
1160
|
-
* ```
|
|
1161
|
-
*/
|
|
1162
464
|
const dateUTC = _Date.UTC;
|
|
1163
465
|
|
|
1164
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1165
|
-
/**
|
|
1166
|
-
* Creates a wrapper function that only executes the wrapped function if the condition function returns true.
|
|
1167
|
-
*
|
|
1168
|
-
* @param func - The function to be conditionally executed.
|
|
1169
|
-
* @param conditionFunc - A function that returns a boolean, determining if `func` should be executed.
|
|
1170
|
-
* @returns A wrapped version of `func` that executes conditionally.
|
|
1171
|
-
*
|
|
1172
|
-
* @example Conditional logging based on flag
|
|
1173
|
-
* ```typescript
|
|
1174
|
-
* let enabled = false
|
|
1175
|
-
* const conditionalLog = createConditionalExecutionFunction(console.log, () => enabled)
|
|
1176
|
-
* conditionalLog('test') // does nothing
|
|
1177
|
-
* enabled = true
|
|
1178
|
-
* conditionalLog('test') // logs 'test'
|
|
1179
|
-
* ```
|
|
1180
|
-
*/
|
|
1181
466
|
function createConditionalExecutionFunction(func, conditionFunc) {
|
|
1182
467
|
return function (...args) {
|
|
1183
468
|
if (conditionFunc()) {
|
|
@@ -1186,77 +471,19 @@
|
|
|
1186
471
|
};
|
|
1187
472
|
}
|
|
1188
473
|
|
|
1189
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1190
|
-
/**
|
|
1191
|
-
* Creates a wrapper function that silently ignores any errors thrown by the wrapped void function.
|
|
1192
|
-
* This function is specifically for wrapping functions that do not return a value (void functions).
|
|
1193
|
-
* Exceptions are swallowed without any logging or handling.
|
|
1194
|
-
*
|
|
1195
|
-
* @param func - The void function to be wrapped.
|
|
1196
|
-
* @returns A wrapped version of the input function that ignores errors.
|
|
1197
|
-
*
|
|
1198
|
-
* @example Safely parsing invalid JSON
|
|
1199
|
-
* ```typescript
|
|
1200
|
-
* const safeParse = createErrorIgnoringFunction(() => JSON.parse('invalid'))
|
|
1201
|
-
* safeParse() // silently fails without throwing
|
|
1202
|
-
* ```
|
|
1203
|
-
*/
|
|
1204
474
|
function createErrorIgnoringFunction(func) {
|
|
1205
475
|
return function (...args) {
|
|
1206
476
|
try {
|
|
1207
477
|
func(...args);
|
|
1208
478
|
}
|
|
1209
479
|
catch {
|
|
1210
|
-
// Deliberately swallowing/ignoring the exception
|
|
1211
480
|
}
|
|
1212
481
|
};
|
|
1213
482
|
}
|
|
1214
483
|
|
|
1215
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1216
|
-
/**
|
|
1217
|
-
* A no-operation function (noop) that does nothing regardless of the arguments passed.
|
|
1218
|
-
* It is designed to be as permissive as possible in its typing without using the `Function` keyword.
|
|
1219
|
-
*
|
|
1220
|
-
* @param args - Any arguments passed to the function (ignored)
|
|
1221
|
-
*
|
|
1222
|
-
* @example Using noop as fallback callback
|
|
1223
|
-
* ```typescript
|
|
1224
|
-
* const callback = condition ? handleEvent : noop
|
|
1225
|
-
* callback() // safely does nothing if condition is false
|
|
1226
|
-
* ```
|
|
1227
|
-
*/
|
|
1228
484
|
const noop = (...args) => {
|
|
1229
|
-
// Intentionally does nothing
|
|
1230
485
|
};
|
|
1231
486
|
|
|
1232
|
-
/**
|
|
1233
|
-
* Creates a logger instance with configurable log level filtering.
|
|
1234
|
-
* Each log function is wrapped to respect the current log level setting.
|
|
1235
|
-
*
|
|
1236
|
-
* @param error - Function to handle error-level logs (required)
|
|
1237
|
-
* @param warn - Function to handle warning-level logs (optional, defaults to noop)
|
|
1238
|
-
* @param log - Function to handle standard logs (optional, defaults to noop)
|
|
1239
|
-
* @param info - Function to handle info-level logs (optional, defaults to noop)
|
|
1240
|
-
* @param debug - Function to handle debug-level logs (optional, defaults to noop)
|
|
1241
|
-
* @returns A frozen logger object with log methods, level control, channel, and timing helpers
|
|
1242
|
-
* @throws {ErrorLevelFn} When any provided log function is invalid
|
|
1243
|
-
*
|
|
1244
|
-
* @example Creating a logger with log level filtering
|
|
1245
|
-
* ```typescript
|
|
1246
|
-
* const logger = createLogger(console.error, console.warn, console.log)
|
|
1247
|
-
* logger.setLogLevel('warn')
|
|
1248
|
-
* logger.warn('Connection timeout') // logs
|
|
1249
|
-
* logger.info('Request complete') // suppressed (below 'warn' level)
|
|
1250
|
-
* ```
|
|
1251
|
-
*
|
|
1252
|
-
* @example Channeling and timing
|
|
1253
|
-
* ```typescript
|
|
1254
|
-
* const logger = createLogger(console.error, console.warn, console.log, console.info, console.debug)
|
|
1255
|
-
* logger.setLogLevel('debug')
|
|
1256
|
-
* const build = logger.channel('build')
|
|
1257
|
-
* await build.timedAsync('bundle', async () => bundle())
|
|
1258
|
-
* ```
|
|
1259
|
-
*/
|
|
1260
487
|
function createLogger$1(error, warn = noop, log = noop, info = noop, debug = noop) {
|
|
1261
488
|
if (notValidLogFn(error)) {
|
|
1262
489
|
throw createError(notFnMsg('error'));
|
|
@@ -1291,20 +518,11 @@
|
|
|
1291
518
|
};
|
|
1292
519
|
return createPrefixedLogger(core, '');
|
|
1293
520
|
}
|
|
1294
|
-
/**
|
|
1295
|
-
* Creates a Logger that prepends `[fullPrefix]` to every log call. When `fullPrefix`
|
|
1296
|
-
* is empty, returns a Logger that delegates directly to the core functions.
|
|
1297
|
-
*
|
|
1298
|
-
* @param core - Shared level-aware log functions and level controls.
|
|
1299
|
-
* @param fullPrefix - Channel chain joined with `:`, or `''` for the root logger.
|
|
1300
|
-
* @returns A frozen Logger that emits with the resolved prefix.
|
|
1301
|
-
*/
|
|
1302
521
|
function createPrefixedLogger(core, fullPrefix) {
|
|
1303
522
|
const tag = fullPrefix ? `[${fullPrefix}]` : '';
|
|
1304
523
|
const prefixed = (fn) => {
|
|
1305
524
|
if (!tag)
|
|
1306
525
|
return fn;
|
|
1307
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1308
526
|
return (...data) => fn(tag, ...data);
|
|
1309
527
|
};
|
|
1310
528
|
const instance = freeze({
|
|
@@ -1325,14 +543,6 @@
|
|
|
1325
543
|
});
|
|
1326
544
|
return instance;
|
|
1327
545
|
}
|
|
1328
|
-
/**
|
|
1329
|
-
* Times a sync call, logging completion at debug level or failure at error level.
|
|
1330
|
-
*
|
|
1331
|
-
* @param logger - The logger used to emit the timing message.
|
|
1332
|
-
* @param label - Human-readable label for the timed operation.
|
|
1333
|
-
* @param fn - The synchronous operation to invoke.
|
|
1334
|
-
* @returns The value returned by `fn`.
|
|
1335
|
-
*/
|
|
1336
546
|
function runTimed(logger, label, fn) {
|
|
1337
547
|
const start = dateNow();
|
|
1338
548
|
try {
|
|
@@ -1347,15 +557,6 @@
|
|
|
1347
557
|
throw error;
|
|
1348
558
|
}
|
|
1349
559
|
}
|
|
1350
|
-
/**
|
|
1351
|
-
* Times an async call, logging completion at debug level or failure at error level.
|
|
1352
|
-
* On rejection with an `Error` carrying a stack, the stack is also dumped at debug level.
|
|
1353
|
-
*
|
|
1354
|
-
* @param logger - The logger used to emit the timing message.
|
|
1355
|
-
* @param label - Human-readable label for the timed operation.
|
|
1356
|
-
* @param fn - The async operation to invoke.
|
|
1357
|
-
* @returns The value resolved by `fn`'s promise.
|
|
1358
|
-
*/
|
|
1359
560
|
async function runTimedAsync(logger, label, fn) {
|
|
1360
561
|
const start = dateNow();
|
|
1361
562
|
try {
|
|
@@ -1373,144 +574,42 @@
|
|
|
1373
574
|
throw error;
|
|
1374
575
|
}
|
|
1375
576
|
}
|
|
1376
|
-
/**
|
|
1377
|
-
* Extracts a human-readable message from an unknown thrown value.
|
|
1378
|
-
*
|
|
1379
|
-
* @param error - The unknown value caught from a try/catch or rejected promise.
|
|
1380
|
-
* @returns The error's `message` when it is an `Error`, otherwise its string coercion.
|
|
1381
|
-
*/
|
|
1382
577
|
function describeError(error) {
|
|
1383
578
|
return error instanceof Error ? error.message : String(error);
|
|
1384
579
|
}
|
|
1385
|
-
/**
|
|
1386
|
-
* Validates whether a given value is a valid log function.
|
|
1387
|
-
*
|
|
1388
|
-
* @param fn - The value to validate
|
|
1389
|
-
* @returns True if the value is not a function (invalid), false if it is valid
|
|
1390
|
-
*/
|
|
1391
580
|
function notValidLogFn(fn) {
|
|
1392
581
|
return getType(fn) !== 'function' && fn !== noop;
|
|
1393
582
|
}
|
|
1394
|
-
/**
|
|
1395
|
-
* Generates an error message for invalid log function parameters.
|
|
1396
|
-
*
|
|
1397
|
-
* @param label - The name of the log function that failed validation
|
|
1398
|
-
* @returns A formatted error message string
|
|
1399
|
-
*/
|
|
1400
583
|
function notFnMsg(label) {
|
|
1401
584
|
return `Cannot create a logger when ${label} is not a function`;
|
|
1402
585
|
}
|
|
1403
586
|
|
|
1404
|
-
/**
|
|
1405
|
-
* Safe copies of Console built-in methods.
|
|
1406
|
-
*
|
|
1407
|
-
* These references are captured at module initialization time to protect against
|
|
1408
|
-
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
1409
|
-
*
|
|
1410
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/console
|
|
1411
|
-
*/
|
|
1412
587
|
const _console = globalThis.console;
|
|
1413
|
-
/**
|
|
1414
|
-
* (Safe copy) Outputs a message to the console.
|
|
1415
|
-
*/
|
|
1416
588
|
const log = _console.log.bind(_console);
|
|
1417
|
-
/**
|
|
1418
|
-
* (Safe copy) Outputs a warning message to the console.
|
|
1419
|
-
*/
|
|
1420
589
|
const warn = _console.warn.bind(_console);
|
|
1421
|
-
/**
|
|
1422
|
-
* (Safe copy) Outputs an error message to the console.
|
|
1423
|
-
*/
|
|
1424
590
|
const error = _console.error.bind(_console);
|
|
1425
|
-
/**
|
|
1426
|
-
* (Safe copy) Outputs an informational message to the console.
|
|
1427
|
-
*/
|
|
1428
591
|
const info = _console.info.bind(_console);
|
|
1429
|
-
/**
|
|
1430
|
-
* (Safe copy) Outputs a debug message to the console.
|
|
1431
|
-
*/
|
|
1432
592
|
const debug = _console.debug.bind(_console);
|
|
1433
|
-
/**
|
|
1434
|
-
* (Safe copy) Outputs a stack trace to the console.
|
|
1435
|
-
*/
|
|
1436
593
|
_console.trace.bind(_console);
|
|
1437
|
-
/**
|
|
1438
|
-
* (Safe copy) Displays an interactive listing of the properties of a specified object.
|
|
1439
|
-
*/
|
|
1440
594
|
_console.dir.bind(_console);
|
|
1441
|
-
/**
|
|
1442
|
-
* (Safe copy) Displays tabular data as a table.
|
|
1443
|
-
*/
|
|
1444
595
|
_console.table.bind(_console);
|
|
1445
|
-
/**
|
|
1446
|
-
* (Safe copy) Writes an error message to the console if the assertion is false.
|
|
1447
|
-
*/
|
|
1448
596
|
_console.assert.bind(_console);
|
|
1449
|
-
/**
|
|
1450
|
-
* (Safe copy) Clears the console.
|
|
1451
|
-
*/
|
|
1452
597
|
_console.clear.bind(_console);
|
|
1453
|
-
/**
|
|
1454
|
-
* (Safe copy) Logs the number of times that this particular call to count() has been called.
|
|
1455
|
-
*/
|
|
1456
598
|
_console.count.bind(_console);
|
|
1457
|
-
/**
|
|
1458
|
-
* (Safe copy) Resets the counter used with console.count().
|
|
1459
|
-
*/
|
|
1460
599
|
_console.countReset.bind(_console);
|
|
1461
|
-
/**
|
|
1462
|
-
* (Safe copy) Creates a new inline group in the console.
|
|
1463
|
-
*/
|
|
1464
600
|
_console.group.bind(_console);
|
|
1465
|
-
/**
|
|
1466
|
-
* (Safe copy) Creates a new inline group in the console that is initially collapsed.
|
|
1467
|
-
*/
|
|
1468
601
|
_console.groupCollapsed.bind(_console);
|
|
1469
|
-
/**
|
|
1470
|
-
* (Safe copy) Exits the current inline group.
|
|
1471
|
-
*/
|
|
1472
602
|
_console.groupEnd.bind(_console);
|
|
1473
|
-
/**
|
|
1474
|
-
* (Safe copy) Starts a timer with a name specified as an input parameter.
|
|
1475
|
-
*/
|
|
1476
603
|
_console.time.bind(_console);
|
|
1477
|
-
/**
|
|
1478
|
-
* (Safe copy) Stops a timer that was previously started.
|
|
1479
|
-
*/
|
|
1480
604
|
_console.timeEnd.bind(_console);
|
|
1481
|
-
/**
|
|
1482
|
-
* (Safe copy) Logs the current value of a timer that was previously started.
|
|
1483
|
-
*/
|
|
1484
605
|
_console.timeLog.bind(_console);
|
|
1485
606
|
|
|
1486
607
|
const logger = createLogger$1(error, warn, log, info, debug);
|
|
1487
608
|
|
|
1488
609
|
const DEFAULT_PREFIX = '[nexus]';
|
|
1489
|
-
/**
|
|
1490
|
-
* Creates a logger instance configured for nexus.
|
|
1491
|
-
*
|
|
1492
|
-
* If a custom logger is provided, it will be used directly.
|
|
1493
|
-
* Otherwise, a new logger will be created using the logging library.
|
|
1494
|
-
*
|
|
1495
|
-
* @param options - Logger configuration options
|
|
1496
|
-
* @returns Logger instance
|
|
1497
|
-
*
|
|
1498
|
-
* @example Configuring logger options
|
|
1499
|
-
* ```typescript
|
|
1500
|
-
* const logger = createLogger({ level: 'debug', prefix: '[my-channel]' })
|
|
1501
|
-
* logger.debug('Channel initialized')
|
|
1502
|
-
* ```
|
|
1503
|
-
*/
|
|
1504
610
|
function createLogger(options = {}) {
|
|
1505
611
|
return createLoggerInternal(options);
|
|
1506
612
|
}
|
|
1507
|
-
/**
|
|
1508
|
-
* Internal helper to create a logger with given options.
|
|
1509
|
-
*
|
|
1510
|
-
* @param options - Logger configuration options
|
|
1511
|
-
* @returns Configured logger instance
|
|
1512
|
-
* @internal
|
|
1513
|
-
*/
|
|
1514
613
|
const createLoggerInternal = (options) => {
|
|
1515
614
|
const { level = 'error', prefix = DEFAULT_PREFIX, customLogger } = options;
|
|
1516
615
|
if (customLogger)
|
|
@@ -1521,31 +620,10 @@
|
|
|
1521
620
|
return nexusLogger;
|
|
1522
621
|
};
|
|
1523
622
|
|
|
1524
|
-
/**
|
|
1525
|
-
* Safe WeakSet factory for protected weak set construction.
|
|
1526
|
-
*
|
|
1527
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/weak-set
|
|
1528
|
-
*/
|
|
1529
|
-
/* eslint-disable workspace/lib-require-jsdoc-example */
|
|
1530
623
|
const _WeakSet = globalThis.WeakSet;
|
|
1531
|
-
const _Reflect$
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
* Use this instead of `new WeakSet()`.
|
|
1535
|
-
*
|
|
1536
|
-
* @param iterable - Optional iterable of values.
|
|
1537
|
-
* @returns A new WeakSet instance.
|
|
1538
|
-
*/
|
|
1539
|
-
const createWeakSet = (iterable) => _Reflect$3.construct(_WeakSet, iterable ? [iterable] : []);
|
|
1540
|
-
|
|
1541
|
-
/**
|
|
1542
|
-
* Checks whether an object has circular references using WeakSet.
|
|
1543
|
-
* Safe for use with frozen objects since it only reads, never mutates.
|
|
1544
|
-
*
|
|
1545
|
-
* @param value - The value to check
|
|
1546
|
-
* @param seen - WeakSet of already-seen references
|
|
1547
|
-
* @returns True if circular reference detected
|
|
1548
|
-
*/
|
|
624
|
+
const _Reflect$4 = globalThis.Reflect;
|
|
625
|
+
const createWeakSet = (iterable) => _Reflect$4.construct(_WeakSet, iterable ? [iterable] : []);
|
|
626
|
+
|
|
1549
627
|
function hasCircular(value, seen) {
|
|
1550
628
|
if (!isIterable(value))
|
|
1551
629
|
return false;
|
|
@@ -1562,36 +640,12 @@
|
|
|
1562
640
|
}
|
|
1563
641
|
return false;
|
|
1564
642
|
}
|
|
1565
|
-
/**
|
|
1566
|
-
* Asserts that the given value does not contain circular references.
|
|
1567
|
-
* Throws an Error if a circular reference is detected.
|
|
1568
|
-
*
|
|
1569
|
-
* Uses WeakSet-based cycle detection that works safely with frozen objects.
|
|
1570
|
-
*
|
|
1571
|
-
* @param value - The value to check for circular references
|
|
1572
|
-
* @param paramName - Name of the parameter for error messaging
|
|
1573
|
-
* @throws {Error} if circular reference is detected
|
|
1574
|
-
*
|
|
1575
|
-
* @example Checking for circular references
|
|
1576
|
-
* ```typescript
|
|
1577
|
-
* const config = { a: 1, b: 2 }
|
|
1578
|
-
* assertNoCircularRef(config, 'config') // OK
|
|
1579
|
-
*
|
|
1580
|
-
* const circular: Record<string, unknown> = { a: 1 }
|
|
1581
|
-
* circular.self = circular
|
|
1582
|
-
* assertNoCircularRef(circular, 'config') // Throws Error
|
|
1583
|
-
* ```
|
|
1584
|
-
*/
|
|
1585
643
|
function assertNoCircularRef(value, paramName) {
|
|
1586
644
|
if (hasCircular(value, createWeakSet())) {
|
|
1587
645
|
throw createError(`Circular reference detected in parameter "${paramName}"`);
|
|
1588
646
|
}
|
|
1589
647
|
}
|
|
1590
648
|
|
|
1591
|
-
/**
|
|
1592
|
-
* Default channel settings.
|
|
1593
|
-
* Accept any origin, queue messages, inherit contract from broker.
|
|
1594
|
-
*/
|
|
1595
649
|
const DEFAULT_CHANNEL_SETTINGS = freeze({
|
|
1596
650
|
origin: '*',
|
|
1597
651
|
queueMessages: true,
|
|
@@ -1599,23 +653,6 @@
|
|
|
1599
653
|
contract: undefined,
|
|
1600
654
|
});
|
|
1601
655
|
|
|
1602
|
-
/**
|
|
1603
|
-
* Gracefully closes an active channel connection.
|
|
1604
|
-
*
|
|
1605
|
-
* - Only works if channel is currently active
|
|
1606
|
-
* - Sets channel state to inactive
|
|
1607
|
-
* - Optionally notifies the target window
|
|
1608
|
-
* - Fires 'close' event to subscribers
|
|
1609
|
-
*
|
|
1610
|
-
* @param channel - Channel internals with state and dependencies
|
|
1611
|
-
* @param notify - Whether to notify target window (default: true)
|
|
1612
|
-
*
|
|
1613
|
-
* @example Gracefully closing a connection
|
|
1614
|
-
* ```typescript
|
|
1615
|
-
* disconnect(channel, true) // Close and notify target
|
|
1616
|
-
* disconnect(channel, false) // Close silently
|
|
1617
|
-
* ```
|
|
1618
|
-
*/
|
|
1619
656
|
function disconnect(channel, notify = true) {
|
|
1620
657
|
const state = channel.getState();
|
|
1621
658
|
if (!state.active) {
|
|
@@ -1630,22 +667,6 @@
|
|
|
1630
667
|
channel.notifyEvent('close');
|
|
1631
668
|
}
|
|
1632
669
|
|
|
1633
|
-
/**
|
|
1634
|
-
* Cancels a pending connection request.
|
|
1635
|
-
*
|
|
1636
|
-
* - If channel is closed, sends CANCEL_CONNECTION
|
|
1637
|
-
* - If channel is already open, calls disconnect instead
|
|
1638
|
-
* - Fires 'cancel' event to subscribers
|
|
1639
|
-
*
|
|
1640
|
-
* @param channel - Channel internals with state and dependencies
|
|
1641
|
-
* @param notify - Whether to notify target window (default: true)
|
|
1642
|
-
*
|
|
1643
|
-
* @example Canceling a pending connection
|
|
1644
|
-
* ```typescript
|
|
1645
|
-
* cancel(channel, true) // Cancel and notify target
|
|
1646
|
-
* cancel(channel, false) // Cancel silently
|
|
1647
|
-
* ```
|
|
1648
|
-
*/
|
|
1649
670
|
function cancel(channel, notify = true) {
|
|
1650
671
|
const state = channel.getState();
|
|
1651
672
|
if (state.active) {
|
|
@@ -1660,19 +681,6 @@
|
|
|
1660
681
|
channel.notifyEvent('cancel');
|
|
1661
682
|
}
|
|
1662
683
|
|
|
1663
|
-
/**
|
|
1664
|
-
* Clears all queued messages from the channel.
|
|
1665
|
-
* Returns a new state object with empty queue (immutable update).
|
|
1666
|
-
*
|
|
1667
|
-
* @param state - Current channel state
|
|
1668
|
-
* @returns New state with cleared message queue
|
|
1669
|
-
*
|
|
1670
|
-
* @example Clearing all queued messages
|
|
1671
|
-
* ```typescript
|
|
1672
|
-
* const clearedState = clearQueue(channelState)
|
|
1673
|
-
* // => { ...channelState, queuedMessages: [] }
|
|
1674
|
-
* ```
|
|
1675
|
-
*/
|
|
1676
684
|
function clearQueue(state) {
|
|
1677
685
|
return freeze({
|
|
1678
686
|
...state,
|
|
@@ -1680,21 +688,6 @@
|
|
|
1680
688
|
});
|
|
1681
689
|
}
|
|
1682
690
|
|
|
1683
|
-
/**
|
|
1684
|
-
* Adds a message to the channel's queue.
|
|
1685
|
-
* Returns a new state object with the message appended (immutable update).
|
|
1686
|
-
*
|
|
1687
|
-
* @param state - Current channel state
|
|
1688
|
-
* @param message - Message to queue
|
|
1689
|
-
* @returns New state with message added to queue
|
|
1690
|
-
*
|
|
1691
|
-
* @example Adding a message to the queue
|
|
1692
|
-
* ```typescript
|
|
1693
|
-
* const message = { type: 'data', payload: { userId: 123 } }
|
|
1694
|
-
* const updatedState = queueMessage(channelState, message)
|
|
1695
|
-
* // => { ...channelState, queuedMessages: [...existingMessages, message] }
|
|
1696
|
-
* ```
|
|
1697
|
-
*/
|
|
1698
691
|
function queueMessage(state, message) {
|
|
1699
692
|
return freeze({
|
|
1700
693
|
...state,
|
|
@@ -1702,33 +695,12 @@
|
|
|
1702
695
|
});
|
|
1703
696
|
}
|
|
1704
697
|
|
|
1705
|
-
/**
|
|
1706
|
-
* Queues a message for delivery when the channel opens.
|
|
1707
|
-
*
|
|
1708
|
-
* Messages are stored in the channel state and will be sent
|
|
1709
|
-
* automatically when the channel becomes active via the flush operation.
|
|
1710
|
-
*
|
|
1711
|
-
* @param channel - Channel internals with state and dependencies
|
|
1712
|
-
* @param message - Message to queue
|
|
1713
|
-
*
|
|
1714
|
-
* @example Queueing a message
|
|
1715
|
-
* ```typescript
|
|
1716
|
-
* queue(channel, { type: 'GREETING', data: 'Hello' })
|
|
1717
|
-
* ```
|
|
1718
|
-
*/
|
|
1719
698
|
function queue(channel, message) {
|
|
1720
699
|
const state = channel.getState();
|
|
1721
700
|
const newState = queueMessage(state, message);
|
|
1722
701
|
channel.updateState(newState);
|
|
1723
702
|
}
|
|
1724
703
|
|
|
1725
|
-
/**
|
|
1726
|
-
* Action types that should always be sent in plaintext.
|
|
1727
|
-
*
|
|
1728
|
-
* Handshake actions must remain unencrypted because:
|
|
1729
|
-
* - Security negotiation happens during handshake
|
|
1730
|
-
* - Both parties need to read handshake messages before security is established
|
|
1731
|
-
*/
|
|
1732
704
|
const PLAINTEXT_ACTION_TYPES = createSet([
|
|
1733
705
|
ACTION_TYPES.REQUEST_CONNECTION,
|
|
1734
706
|
ACTION_TYPES.ACCEPT_CONNECTION,
|
|
@@ -1737,26 +709,6 @@
|
|
|
1737
709
|
ACTION_TYPES.CANCEL_CONNECTION_ACKNOWLEDGED,
|
|
1738
710
|
ACTION_TYPES.OPEN_CONNECTION,
|
|
1739
711
|
]);
|
|
1740
|
-
/**
|
|
1741
|
-
* Sends a raw action to the channel's target window.
|
|
1742
|
-
*
|
|
1743
|
-
* This function routes actions through the security transport when:
|
|
1744
|
-
* - The channel has a security transport configured
|
|
1745
|
-
* - The security transport is ready
|
|
1746
|
-
* - The action type is not a handshake action (handshakes are always plaintext)
|
|
1747
|
-
*
|
|
1748
|
-
* For secure protocols (v1/v2), the action is encrypted and sent as Uint8Array.
|
|
1749
|
-
* For 'none' protocol or handshake actions, the action is sent as plain object.
|
|
1750
|
-
*
|
|
1751
|
-
* @param channel - Channel internals with state and dependencies
|
|
1752
|
-
* @param action - Action to send
|
|
1753
|
-
*
|
|
1754
|
-
* @example Sending an action to the target window
|
|
1755
|
-
* ```typescript
|
|
1756
|
-
* const action = channel.actions.requestConnection(processId)
|
|
1757
|
-
* sendAction(channel, action)
|
|
1758
|
-
* ```
|
|
1759
|
-
*/
|
|
1760
712
|
function sendAction(channel, action) {
|
|
1761
713
|
const state = channel.getState();
|
|
1762
714
|
if (!action || typeof action.type !== 'string') {
|
|
@@ -1771,29 +723,6 @@
|
|
|
1771
723
|
state.target.postMessage(action, '*');
|
|
1772
724
|
}
|
|
1773
725
|
|
|
1774
|
-
/**
|
|
1775
|
-
* Sends a typed message through an active channel.
|
|
1776
|
-
*
|
|
1777
|
-
* Message routing behavior:
|
|
1778
|
-
* - If channel is closed and queueMessages is enabled, queues the message
|
|
1779
|
-
* - If channel is closed and queueMessages is disabled, throws error
|
|
1780
|
-
* - If security transport exists but is not ready, queues the message
|
|
1781
|
-
* - If channel is open and security is ready (or protocol is 'none'), sends message
|
|
1782
|
-
*
|
|
1783
|
-
* For secure protocols (v1/v2), the message is routed through the security
|
|
1784
|
-
* transport which encrypts and sends as Uint8Array via postMessage.
|
|
1785
|
-
*
|
|
1786
|
-
* @param channel - Channel internals with state and dependencies
|
|
1787
|
-
* @param message - Message to send with type and data
|
|
1788
|
-
*
|
|
1789
|
-
* @throws {Error} If channel is closed and queueing is disabled
|
|
1790
|
-
* @throws {Error} If message type is not accepted in channel contract
|
|
1791
|
-
*
|
|
1792
|
-
* @example Sending a typed message
|
|
1793
|
-
* ```typescript
|
|
1794
|
-
* send(channel, { type: 'USER_ACTION', data: { userId: 123 } })
|
|
1795
|
-
* ```
|
|
1796
|
-
*/
|
|
1797
726
|
function send(channel, message) {
|
|
1798
727
|
const state = channel.getState();
|
|
1799
728
|
if (!state.active) {
|
|
@@ -1816,24 +745,11 @@
|
|
|
1816
745
|
if (!emittedTypes.includes(message.type)) {
|
|
1817
746
|
throw createError(`Cannot send message to ${state.name} channel. Message type '${message.type}' is not in the emitted actions of channel contract.`);
|
|
1818
747
|
}
|
|
1819
|
-
const action = channel.actions.newMessage(message
|
|
748
|
+
const action = channel.actions.newMessage(message);
|
|
1820
749
|
sendAction(channel, action);
|
|
1821
750
|
channel.notifyMessage(message);
|
|
1822
751
|
}
|
|
1823
752
|
|
|
1824
|
-
/**
|
|
1825
|
-
* Sends all queued messages and clears the queue.
|
|
1826
|
-
*
|
|
1827
|
-
* Called automatically when a channel opens if queueMessages is enabled.
|
|
1828
|
-
* Messages are sent in FIFO order.
|
|
1829
|
-
*
|
|
1830
|
-
* @param channel - Channel internals with state and dependencies
|
|
1831
|
-
*
|
|
1832
|
-
* @example Flushing queued messages
|
|
1833
|
-
* ```typescript
|
|
1834
|
-
* flush(channel) // Sends all queued messages
|
|
1835
|
-
* ```
|
|
1836
|
-
*/
|
|
1837
753
|
function flush(channel) {
|
|
1838
754
|
const state = channel.getState();
|
|
1839
755
|
for (const message of state.queuedMessages) {
|
|
@@ -1850,20 +766,6 @@
|
|
|
1850
766
|
channel.updateState(newState);
|
|
1851
767
|
}
|
|
1852
768
|
|
|
1853
|
-
/**
|
|
1854
|
-
* Initiates the connection handshake for a channel.
|
|
1855
|
-
*
|
|
1856
|
-
* - If channel is already open, does nothing
|
|
1857
|
-
* - If channel has a scheduled activation (pending connection), accepts it
|
|
1858
|
-
* - Otherwise, sends REQUEST_CONNECTION to initiate handshake
|
|
1859
|
-
*
|
|
1860
|
-
* @param channel - Channel internals with state and dependencies
|
|
1861
|
-
*
|
|
1862
|
-
* @example Initiating a connection
|
|
1863
|
-
* ```typescript
|
|
1864
|
-
* connect(channel) // Sends REQUEST_CONNECTION or accepts pending
|
|
1865
|
-
* ```
|
|
1866
|
-
*/
|
|
1867
769
|
function connect(channel) {
|
|
1868
770
|
const state = channel.getState();
|
|
1869
771
|
if (state.active) {
|
|
@@ -1905,24 +807,6 @@
|
|
|
1905
807
|
channel.sendAction(requestAction);
|
|
1906
808
|
}
|
|
1907
809
|
|
|
1908
|
-
/**
|
|
1909
|
-
* Immediately destroys a channel and removes it from the broker.
|
|
1910
|
-
*
|
|
1911
|
-
* - Sets channel to inactive immediately
|
|
1912
|
-
* - Optionally notifies the target window
|
|
1913
|
-
* - Removes channel from all registries
|
|
1914
|
-
* - Fires 'destroy' event to subscribers
|
|
1915
|
-
* - This is irreversible - channel cannot be reconnected
|
|
1916
|
-
*
|
|
1917
|
-
* @param channel - Channel internals with state and dependencies
|
|
1918
|
-
* @param notify - Whether to notify target window (default: true)
|
|
1919
|
-
*
|
|
1920
|
-
* @example Destroying a channel
|
|
1921
|
-
* ```typescript
|
|
1922
|
-
* destroy(channel, true) // Destroy and notify target
|
|
1923
|
-
* destroy(channel, false) // Destroy silently
|
|
1924
|
-
* ```
|
|
1925
|
-
*/
|
|
1926
810
|
function destroy(channel, notify = true) {
|
|
1927
811
|
channel.updateState({ active: false });
|
|
1928
812
|
if (notify) {
|
|
@@ -1934,22 +818,6 @@
|
|
|
1934
818
|
}
|
|
1935
819
|
}
|
|
1936
820
|
|
|
1937
|
-
/**
|
|
1938
|
-
* Activates a channel by setting it as active and recording connection details.
|
|
1939
|
-
* Returns a new state object (immutable update).
|
|
1940
|
-
*
|
|
1941
|
-
* @param state - Current channel state
|
|
1942
|
-
* @param origin - Origin of the connected channel
|
|
1943
|
-
* @param contract - Negotiated channel contract
|
|
1944
|
-
* @returns New state with channel activated
|
|
1945
|
-
*
|
|
1946
|
-
* @example Activating a channel with contract
|
|
1947
|
-
* ```typescript
|
|
1948
|
-
* const contract = { accepted: [{ type: 'message' }] }
|
|
1949
|
-
* const activeState = activate(channelState, 'https://example.com', contract)
|
|
1950
|
-
* // => { ...channelState, active: true, origin: 'https://example.com', ... }
|
|
1951
|
-
* ```
|
|
1952
|
-
*/
|
|
1953
821
|
function activate(state, origin, contract) {
|
|
1954
822
|
const acceptedActions = (contract.accepted || []).map((action) => action.type);
|
|
1955
823
|
return freeze({
|
|
@@ -1963,21 +831,6 @@
|
|
|
1963
831
|
});
|
|
1964
832
|
}
|
|
1965
833
|
|
|
1966
|
-
/**
|
|
1967
|
-
* Creates the initial state for a new channel.
|
|
1968
|
-
* All collections are empty, all optional fields are null.
|
|
1969
|
-
*
|
|
1970
|
-
* @param name - Channel name/identifier
|
|
1971
|
-
* @param target - Target window for communication
|
|
1972
|
-
* @param settings - Channel settings (queueMessages, debug, logger, etc.)
|
|
1973
|
-
* @returns Fresh channel state object
|
|
1974
|
-
*
|
|
1975
|
-
* @example Creating initial channel state
|
|
1976
|
-
* ```typescript
|
|
1977
|
-
* const state = createInitialState('my-channel', targetWindow, { queueMessages: true })
|
|
1978
|
-
* // => { id: '...', name: 'my-channel', active: false, queuedMessages: [], ... }
|
|
1979
|
-
* ```
|
|
1980
|
-
*/
|
|
1981
834
|
function createInitialState(name, target, settings) {
|
|
1982
835
|
return freeze({
|
|
1983
836
|
id: uuidV4(),
|
|
@@ -2003,26 +856,6 @@
|
|
|
2003
856
|
});
|
|
2004
857
|
}
|
|
2005
858
|
|
|
2006
|
-
/**
|
|
2007
|
-
* Implementation of subscribeToEvents that handles both overload signatures.
|
|
2008
|
-
*
|
|
2009
|
-
* @param channel - The channel internals object
|
|
2010
|
-
* @param eventOrHandler - Either an event name or a handler function
|
|
2011
|
-
* @param handler - Handler function when event name is provided
|
|
2012
|
-
* @returns Unsubscribe function to remove the handler
|
|
2013
|
-
*
|
|
2014
|
-
* @example Subscribing to all events with cleanup
|
|
2015
|
-
* ```typescript
|
|
2016
|
-
* const unsubscribe = subscribeToEvents(channel, (event, data) => {
|
|
2017
|
-
* if (event === 'open') {
|
|
2018
|
-
* console.log('Channel opened:', data)
|
|
2019
|
-
* }
|
|
2020
|
-
* })
|
|
2021
|
-
*
|
|
2022
|
-
* // Cleanup when no longer needed
|
|
2023
|
-
* unsubscribe()
|
|
2024
|
-
* ```
|
|
2025
|
-
*/
|
|
2026
859
|
function subscribeToEvents(channel, eventOrHandler, handler) {
|
|
2027
860
|
const isEventSpecific = typeof eventOrHandler === 'string' && typeof handler === 'function';
|
|
2028
861
|
let wrappedHandler;
|
|
@@ -2054,26 +887,6 @@
|
|
|
2054
887
|
};
|
|
2055
888
|
}
|
|
2056
889
|
|
|
2057
|
-
/**
|
|
2058
|
-
* Subscribes to incoming messages on the channel.
|
|
2059
|
-
*
|
|
2060
|
-
* Handler will be called with each message received from the target window.
|
|
2061
|
-
*
|
|
2062
|
-
* @param channel - Channel internals with state and dependencies
|
|
2063
|
-
* @param handler - Message handler function
|
|
2064
|
-
* @returns Unsubscribe function to remove the handler
|
|
2065
|
-
*
|
|
2066
|
-
* @throws {Error} If handler is not a function
|
|
2067
|
-
*
|
|
2068
|
-
* @example Subscribing to channel messages
|
|
2069
|
-
* ```typescript
|
|
2070
|
-
* const unsubscribe = subscribeToMessages(channel, (message) => {
|
|
2071
|
-
* console.log('Message:', message.type, message.data)
|
|
2072
|
-
* })
|
|
2073
|
-
*
|
|
2074
|
-
* // Later: unsubscribe()
|
|
2075
|
-
* ```
|
|
2076
|
-
*/
|
|
2077
890
|
function subscribeToMessages(channel, handler) {
|
|
2078
891
|
if (typeof handler !== 'function') {
|
|
2079
892
|
throw createError('Expected callback function.');
|
|
@@ -2088,32 +901,10 @@
|
|
|
2088
901
|
};
|
|
2089
902
|
}
|
|
2090
903
|
|
|
2091
|
-
/**
|
|
2092
|
-
* Logs a channel event in a structured format.
|
|
2093
|
-
*
|
|
2094
|
-
* @param logger - Logger instance to use
|
|
2095
|
-
* @param event - Type of channel event that occurred
|
|
2096
|
-
* @param data - Additional data associated with the event
|
|
2097
|
-
*/
|
|
2098
904
|
function logEvent(logger, event, data) {
|
|
2099
905
|
logger.debug(`Channel event:`, event, data);
|
|
2100
906
|
}
|
|
2101
907
|
|
|
2102
|
-
/**
|
|
2103
|
-
* Notifies all event subscribers of a channel event.
|
|
2104
|
-
*
|
|
2105
|
-
* Calls each subscribed event handler with the event type, optional data, and channel JSON.
|
|
2106
|
-
* Errors in handlers are caught and logged to prevent breaking other handlers.
|
|
2107
|
-
*
|
|
2108
|
-
* @param channel - Channel internals with state and dependencies
|
|
2109
|
-
* @param event - Event type that occurred
|
|
2110
|
-
* @param data - Optional event data
|
|
2111
|
-
*
|
|
2112
|
-
* @example Notifying subscribers of an event
|
|
2113
|
-
* ```typescript
|
|
2114
|
-
* notifyEvent(channel, 'open', { timestamp: Date.now() })
|
|
2115
|
-
* ```
|
|
2116
|
-
*/
|
|
2117
908
|
function notifyEvent(channel, event, data) {
|
|
2118
909
|
const state = channel.getState();
|
|
2119
910
|
if (state.logger) {
|
|
@@ -2140,20 +931,6 @@
|
|
|
2140
931
|
}
|
|
2141
932
|
}
|
|
2142
933
|
|
|
2143
|
-
/**
|
|
2144
|
-
* Notifies all message subscribers of an incoming message.
|
|
2145
|
-
*
|
|
2146
|
-
* Calls each subscribed message handler with the message data.
|
|
2147
|
-
* Errors in handlers are caught and logged to prevent breaking other handlers.
|
|
2148
|
-
*
|
|
2149
|
-
* @param channel - Channel internals with state and dependencies
|
|
2150
|
-
* @param message - Message that was received
|
|
2151
|
-
*
|
|
2152
|
-
* @example Notifying subscribers of a message
|
|
2153
|
-
* ```typescript
|
|
2154
|
-
* notifyMessage(channel, { type: 'USER_ACTION', data: { userId: 123 } })
|
|
2155
|
-
* ```
|
|
2156
|
-
*/
|
|
2157
934
|
function notifyMessage(channel, message) {
|
|
2158
935
|
const state = channel.getState();
|
|
2159
936
|
for (const handler of state.messageSubscriptions) {
|
|
@@ -2168,26 +945,6 @@
|
|
|
2168
945
|
}
|
|
2169
946
|
}
|
|
2170
947
|
|
|
2171
|
-
/**
|
|
2172
|
-
* Creates a new message channel.
|
|
2173
|
-
*
|
|
2174
|
-
* Uses functional programming with closures for encapsulation.
|
|
2175
|
-
* Returns a public handle with methods while keeping state private.
|
|
2176
|
-
*
|
|
2177
|
-
* @param config - Channel configuration (name, target, settings)
|
|
2178
|
-
* @param deps - Dependencies (action creators, process manager, cleanup)
|
|
2179
|
-
* @returns Channel handle with public API
|
|
2180
|
-
*
|
|
2181
|
-
* @example Creating and using a channel
|
|
2182
|
-
* ```typescript
|
|
2183
|
-
* const channel = createChannel(
|
|
2184
|
-
* { name: 'my-channel', target: childWindow },
|
|
2185
|
-
* { actions, processManager, cleanup }
|
|
2186
|
-
* )
|
|
2187
|
-
* channel.connect()
|
|
2188
|
-
* channel.send('greet', { message: 'Hello!' })
|
|
2189
|
-
* ```
|
|
2190
|
-
*/
|
|
2191
948
|
function createChannel(config, deps) {
|
|
2192
949
|
assertNoCircularRef(config.settings, 'config.settings');
|
|
2193
950
|
const settings = { ...DEFAULT_CHANNEL_SETTINGS, ...config.settings };
|
|
@@ -2223,6 +980,7 @@
|
|
|
2223
980
|
getName: () => state.name,
|
|
2224
981
|
getTarget: () => state.target,
|
|
2225
982
|
isActive: () => state.active,
|
|
983
|
+
getAcceptedTypes: () => state.acceptedActions,
|
|
2226
984
|
toJSON: () => ({
|
|
2227
985
|
id: state.id,
|
|
2228
986
|
name: state.name,
|
|
@@ -2291,72 +1049,18 @@
|
|
|
2291
1049
|
return freeze(handle);
|
|
2292
1050
|
}
|
|
2293
1051
|
|
|
2294
|
-
/**
|
|
2295
|
-
* Adds a channel to the registry, making it available for lookup
|
|
2296
|
-
* by window, ID, and name.
|
|
2297
|
-
*
|
|
2298
|
-
* @param registry - The channel registry instance
|
|
2299
|
-
* @param channel - Channel to register (must have id, name, target)
|
|
2300
|
-
* @throws {Error} Error if channel is invalid
|
|
2301
|
-
*
|
|
2302
|
-
* @example Registering a channel
|
|
2303
|
-
* ```typescript
|
|
2304
|
-
* const registry = createRegistry()
|
|
2305
|
-
* add(registry, { id: 'ch-1', name: 'main', target: iframe.contentWindow })
|
|
2306
|
-
* ```
|
|
2307
|
-
*/
|
|
2308
1052
|
function add(registry, channel) {
|
|
2309
1053
|
registry.add(channel);
|
|
2310
1054
|
}
|
|
2311
1055
|
|
|
2312
|
-
/**
|
|
2313
|
-
* Finds a channel by its target window.
|
|
2314
|
-
* Uses WeakMap for O(1) lookup that doesn't prevent garbage collection.
|
|
2315
|
-
*
|
|
2316
|
-
* @param registry - The channel registry instance
|
|
2317
|
-
* @param target - The window to look up
|
|
2318
|
-
* @returns Channel if found, undefined otherwise
|
|
2319
|
-
*/
|
|
2320
1056
|
function getByWindow(registry, target) {
|
|
2321
1057
|
return registry.getByWindow(target);
|
|
2322
1058
|
}
|
|
2323
1059
|
|
|
2324
|
-
/**
|
|
2325
|
-
* Removes a channel from the registry, making it unavailable
|
|
2326
|
-
* for all lookup methods.
|
|
2327
|
-
*
|
|
2328
|
-
* @param registry - The channel registry instance
|
|
2329
|
-
* @param channel - Channel to unregister
|
|
2330
|
-
*/
|
|
2331
1060
|
function remove(registry, channel) {
|
|
2332
1061
|
registry.remove(channel);
|
|
2333
1062
|
}
|
|
2334
1063
|
|
|
2335
|
-
/**
|
|
2336
|
-
* Adds a channel to the broker.
|
|
2337
|
-
*
|
|
2338
|
-
* @param state - Current broker state
|
|
2339
|
-
* @param registry - Channel registry for storing and retrieving channels
|
|
2340
|
-
* @param processManager - Process ID manager for tracking communication processes
|
|
2341
|
-
* @param actions - Action creators from broker for managing channel lifecycle
|
|
2342
|
-
* @param name - Unique identifier for the channel
|
|
2343
|
-
* @param target - Target window to communicate with
|
|
2344
|
-
* @param settings - Optional configuration settings for the channel
|
|
2345
|
-
* @returns The created or existing channel
|
|
2346
|
-
*
|
|
2347
|
-
* @example Registering a channel with the broker
|
|
2348
|
-
* ```typescript
|
|
2349
|
-
* const channel = addChannel(
|
|
2350
|
-
* brokerState,
|
|
2351
|
-
* registry,
|
|
2352
|
-
* processManager,
|
|
2353
|
-
* actions,
|
|
2354
|
-
* 'widget-channel',
|
|
2355
|
-
* iframe.contentWindow,
|
|
2356
|
-
* { timeout: 5000 }
|
|
2357
|
-
* )
|
|
2358
|
-
* ```
|
|
2359
|
-
*/
|
|
2360
1064
|
function addChannel(state, registry, processManager, actions, name, target, settings = {}) {
|
|
2361
1065
|
assertNoCircularRef(settings, 'settings');
|
|
2362
1066
|
const existing = getByWindow(registry, target);
|
|
@@ -2383,43 +1087,14 @@
|
|
|
2383
1087
|
return channel;
|
|
2384
1088
|
}
|
|
2385
1089
|
|
|
2386
|
-
/**
|
|
2387
|
-
* Finds a channel by its unique ID.
|
|
2388
|
-
* Uses Map for O(1) lookup.
|
|
2389
|
-
*
|
|
2390
|
-
* @param registry - The channel registry instance
|
|
2391
|
-
* @param id - The channel ID (UUID) to look up
|
|
2392
|
-
* @returns Channel if found, undefined otherwise
|
|
2393
|
-
*/
|
|
2394
1090
|
function getById(registry, id) {
|
|
2395
1091
|
return registry.getById(id);
|
|
2396
1092
|
}
|
|
2397
1093
|
|
|
2398
|
-
/**
|
|
2399
|
-
* Finds a channel by its name.
|
|
2400
|
-
* Uses Map for O(1) lookup.
|
|
2401
|
-
*
|
|
2402
|
-
* @param registry - The channel registry instance
|
|
2403
|
-
* @param name - The channel name to look up
|
|
2404
|
-
* @returns Channel if found, undefined otherwise
|
|
2405
|
-
*/
|
|
2406
1094
|
function getByName(registry, name) {
|
|
2407
1095
|
return registry.getByName(name);
|
|
2408
1096
|
}
|
|
2409
1097
|
|
|
2410
|
-
/**
|
|
2411
|
-
* Gets a channel by reference (id, name, or window)
|
|
2412
|
-
*
|
|
2413
|
-
* @param registry - Channel registry containing all registered channels
|
|
2414
|
-
* @param reference - Channel identifier (id, name, or window object)
|
|
2415
|
-
* @returns The channel if found, null otherwise
|
|
2416
|
-
*
|
|
2417
|
-
* @example Retrieving channels by name or window
|
|
2418
|
-
* ```typescript
|
|
2419
|
-
* const channelByName = getChannel(registry, 'widget-channel')
|
|
2420
|
-
* const channelByWindow = getChannel(registry, iframe.contentWindow)
|
|
2421
|
-
* ```
|
|
2422
|
-
*/
|
|
2423
1098
|
function getChannel(registry, reference) {
|
|
2424
1099
|
if (typeof reference === 'object' && reference !== null) {
|
|
2425
1100
|
const channel = getByWindow(registry, reference);
|
|
@@ -2432,64 +1107,20 @@
|
|
|
2432
1107
|
return null;
|
|
2433
1108
|
}
|
|
2434
1109
|
|
|
2435
|
-
/**
|
|
2436
|
-
* Returns all registered channels as an array.
|
|
2437
|
-
* The order is not guaranteed.
|
|
2438
|
-
*
|
|
2439
|
-
* @param registry - The channel registry instance
|
|
2440
|
-
* @returns Array of all registered channels
|
|
2441
|
-
*
|
|
2442
|
-
* @example Listing registered channels
|
|
2443
|
-
* ```typescript
|
|
2444
|
-
* const registry = createRegistry()
|
|
2445
|
-
* const channels = getAll(registry)
|
|
2446
|
-
* channels.forEach(ch => console.log(ch.name))
|
|
2447
|
-
* ```
|
|
2448
|
-
*/
|
|
2449
1110
|
function getAll(registry) {
|
|
2450
1111
|
return registry.getAll();
|
|
2451
1112
|
}
|
|
2452
1113
|
|
|
2453
|
-
/**
|
|
2454
|
-
* Lists all channels in JSON format
|
|
2455
|
-
*
|
|
2456
|
-
* @param registry - Channel registry containing all registered channels
|
|
2457
|
-
* @returns Array of channel JSON representations
|
|
2458
|
-
*
|
|
2459
|
-
* @example Listing all channels as JSON
|
|
2460
|
-
* ```typescript
|
|
2461
|
-
* const channels = listChannels(registry)
|
|
2462
|
-
* // => [{ id: 'abc-123', name: 'widget', state: 'connected' }, ...]
|
|
2463
|
-
* ```
|
|
2464
|
-
*/
|
|
2465
1114
|
function listChannels(registry) {
|
|
2466
1115
|
const channels = getAll(registry);
|
|
2467
1116
|
return channels.map((channel) => channel.toJSON());
|
|
2468
1117
|
}
|
|
2469
1118
|
|
|
2470
|
-
/**
|
|
2471
|
-
* Removes a channel from the broker
|
|
2472
|
-
*
|
|
2473
|
-
* @param registry - Channel registry from which to remove the channel
|
|
2474
|
-
* @param channel - The channel instance to cleanup and remove
|
|
2475
|
-
*
|
|
2476
|
-
* @example Removing a channel from the broker
|
|
2477
|
-
* ```typescript
|
|
2478
|
-
* const channel = getChannel(registry, 'widget-channel')
|
|
2479
|
-
* if (channel) {
|
|
2480
|
-
* removeChannel(registry, channel)
|
|
2481
|
-
* }
|
|
2482
|
-
* ```
|
|
2483
|
-
*/
|
|
2484
1119
|
function removeChannel(registry, channel) {
|
|
2485
1120
|
channel.destroy(false);
|
|
2486
1121
|
remove(registry, channel);
|
|
2487
1122
|
}
|
|
2488
1123
|
|
|
2489
|
-
/**
|
|
2490
|
-
* Default broker settings
|
|
2491
|
-
* Used when settings are partially provided
|
|
2492
|
-
*/
|
|
2493
1124
|
const defaultBrokerSettings = freeze({
|
|
2494
1125
|
whitelist: freeze([]),
|
|
2495
1126
|
blacklist: freeze([]),
|
|
@@ -2497,21 +1128,6 @@
|
|
|
2497
1128
|
logLevel: 'error',
|
|
2498
1129
|
});
|
|
2499
1130
|
|
|
2500
|
-
/**
|
|
2501
|
-
* Creates a router map for action types to handlers.
|
|
2502
|
-
*
|
|
2503
|
-
* @param handlers - Map of action types to handler functions
|
|
2504
|
-
* @returns Router map
|
|
2505
|
-
*
|
|
2506
|
-
* @example Creating a router for action handlers
|
|
2507
|
-
* ```typescript
|
|
2508
|
-
* const router = createRouter({
|
|
2509
|
-
* 'OPEN': handleOpen,
|
|
2510
|
-
* 'CLOSE': handleClose,
|
|
2511
|
-
* 'MESSAGE': handleMessage,
|
|
2512
|
-
* })
|
|
2513
|
-
* ```
|
|
2514
|
-
*/
|
|
2515
1131
|
function createRouter(handlers) {
|
|
2516
1132
|
const router = createMap();
|
|
2517
1133
|
entries(handlers).forEach(([type, handler]) => {
|
|
@@ -2520,21 +1136,6 @@
|
|
|
2520
1136
|
return router;
|
|
2521
1137
|
}
|
|
2522
1138
|
|
|
2523
|
-
/**
|
|
2524
|
-
* Applies a security policy to a connection request.
|
|
2525
|
-
*
|
|
2526
|
-
* @param policy - The security policy function
|
|
2527
|
-
* @param event - The MessageEvent to validate
|
|
2528
|
-
* @param logger - Logger instance for error reporting
|
|
2529
|
-
* @returns true if policy allows connection, false otherwise
|
|
2530
|
-
*
|
|
2531
|
-
* @example Validating connection requests with security policy
|
|
2532
|
-
* ```typescript
|
|
2533
|
-
* const policy = (event) => event.origin === 'https://trusted.example.com'
|
|
2534
|
-
* const allowed = applyPolicy(policy, messageEvent, logger)
|
|
2535
|
-
* // => true or false
|
|
2536
|
-
* ```
|
|
2537
|
-
*/
|
|
2538
1139
|
function applyPolicy(policy, event, logger) {
|
|
2539
1140
|
try {
|
|
2540
1141
|
const result = policy(event);
|
|
@@ -2546,27 +1147,6 @@
|
|
|
2546
1147
|
}
|
|
2547
1148
|
}
|
|
2548
1149
|
|
|
2549
|
-
/**
|
|
2550
|
-
* Handles ACCEPT_CONNECTION action.
|
|
2551
|
-
* Completes connection handshake from the initiator's side.
|
|
2552
|
-
*
|
|
2553
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
2554
|
-
* @param message - Message event containing the ACCEPT_CONNECTION action
|
|
2555
|
-
*
|
|
2556
|
-
* @remarks
|
|
2557
|
-
* Side Effects:
|
|
2558
|
-
* - Activates the channel
|
|
2559
|
-
* - Extracts negotiated security protocol (if present)
|
|
2560
|
-
* - Stores negotiated protocol in channel state
|
|
2561
|
-
* - Sends OPEN_CONNECTION to complete handshake (with security confirmation)
|
|
2562
|
-
* - Terminates process after activation
|
|
2563
|
-
* - Fires 'open' lifecycle event
|
|
2564
|
-
*
|
|
2565
|
-
* @example Three-way handshake acceptance
|
|
2566
|
-
* Second step of three-way handshake:
|
|
2567
|
-
* Initiator <- ACCEPT (this handler) <- Responder
|
|
2568
|
-
* Initiator -> OPEN -> Responder
|
|
2569
|
-
*/
|
|
2570
1150
|
function handleAccept(context, message) {
|
|
2571
1151
|
const { state, processManager, logger } = context;
|
|
2572
1152
|
const action = message.data;
|
|
@@ -2629,33 +1209,22 @@
|
|
|
2629
1209
|
channel.notifyEvent('open', { origin: message.origin, contract });
|
|
2630
1210
|
}
|
|
2631
1211
|
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
* - Terminates process
|
|
2644
|
-
* - Fires 'cancel' lifecycle event
|
|
2645
|
-
*
|
|
2646
|
-
* @example Cancellation flow during connection
|
|
2647
|
-
* Cancel flow (before connection completes):
|
|
2648
|
-
* Side A -> CANCEL_CONNECTION
|
|
2649
|
-
* Side B <- CANCEL (this handler)
|
|
2650
|
-
* Side B -> CANCEL_ACKNOWLEDGED
|
|
2651
|
-
* Both sides fire 'cancel' event
|
|
2652
|
-
*/
|
|
1212
|
+
function resolveChannel(registry, message) {
|
|
1213
|
+
const source = message.source;
|
|
1214
|
+
if (source) {
|
|
1215
|
+
const channel = registry.getByWindow(source);
|
|
1216
|
+
if (channel) {
|
|
1217
|
+
return channel;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
return registry.getById(message.data.senderId);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
2653
1223
|
function handleCancel(context, message) {
|
|
2654
1224
|
const { state, registry, processManager } = context;
|
|
2655
1225
|
const action = message.data;
|
|
2656
|
-
const senderId = action['senderId'];
|
|
2657
1226
|
const processId = action['processId'];
|
|
2658
|
-
const channel = (
|
|
1227
|
+
const channel = (resolveChannel(registry, message) || processManager.get(processId));
|
|
2659
1228
|
if (!channel) {
|
|
2660
1229
|
return;
|
|
2661
1230
|
}
|
|
@@ -2669,24 +1238,6 @@
|
|
|
2669
1238
|
channel.notifyEvent('cancel', { notify: true });
|
|
2670
1239
|
}
|
|
2671
1240
|
|
|
2672
|
-
/**
|
|
2673
|
-
* Handles CANCEL_CONNECTION_ACKNOWLEDGED action.
|
|
2674
|
-
* Completes cancellation on initiator's side and notifies cancel event.
|
|
2675
|
-
*
|
|
2676
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
2677
|
-
* @param message - Message event containing the CANCEL_CONNECTION_ACKNOWLEDGED action
|
|
2678
|
-
*
|
|
2679
|
-
* @remarks
|
|
2680
|
-
* Side Effects:
|
|
2681
|
-
* - Terminates the connection process
|
|
2682
|
-
* - Fires 'cancel' lifecycle event on initiator's side
|
|
2683
|
-
*
|
|
2684
|
-
* @example Initiator-side cancellation acknowledgment
|
|
2685
|
-
* Cancellation acknowledgment (initiator side):
|
|
2686
|
-
* Initiator -> CANCEL_CONNECTION
|
|
2687
|
-
* Initiator <- CANCEL_ACKNOWLEDGED (this handler)
|
|
2688
|
-
* Initiator fires 'cancel' event
|
|
2689
|
-
*/
|
|
2690
1241
|
function handleCancelAcknowledged(context, message) {
|
|
2691
1242
|
const { processManager } = context;
|
|
2692
1243
|
const action = message.data;
|
|
@@ -2699,36 +1250,14 @@
|
|
|
2699
1250
|
channel.notifyEvent('cancel', { notify: false });
|
|
2700
1251
|
}
|
|
2701
1252
|
|
|
2702
|
-
/**
|
|
2703
|
-
* Handles CLOSE_CONNECTION action.
|
|
2704
|
-
* Gracefully closes an open connection.
|
|
2705
|
-
*
|
|
2706
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
2707
|
-
* @param message - Message event containing the CLOSE_CONNECTION action
|
|
2708
|
-
*
|
|
2709
|
-
* @remarks
|
|
2710
|
-
* Side Effects:
|
|
2711
|
-
* - Deactivates the channel
|
|
2712
|
-
* - Sends CLOSE_CONNECTION_ACKNOWLEDGED response
|
|
2713
|
-
* - Terminates process
|
|
2714
|
-
* - Fires 'close' lifecycle event
|
|
2715
|
-
*
|
|
2716
|
-
* @example Graceful disconnect flow
|
|
2717
|
-
* Disconnect flow:
|
|
2718
|
-
* Side A -> CLOSE_CONNECTION (initiates)
|
|
2719
|
-
* Side B <- CLOSE_CONNECTION (this handler)
|
|
2720
|
-
* Side B -> CLOSE_ACKNOWLEDGED
|
|
2721
|
-
* Both sides fire 'close' event
|
|
2722
|
-
*/
|
|
2723
1253
|
function handleClose(context, message) {
|
|
2724
1254
|
const { state, registry, processManager } = context;
|
|
2725
1255
|
const action = message.data;
|
|
2726
|
-
const senderId = action.senderId;
|
|
2727
1256
|
if (!('processId' in action)) {
|
|
2728
1257
|
return;
|
|
2729
1258
|
}
|
|
2730
1259
|
const processId = action.processId;
|
|
2731
|
-
const channel =
|
|
1260
|
+
const channel = resolveChannel(registry, message);
|
|
2732
1261
|
if (!channel || !channel.isActive()) {
|
|
2733
1262
|
return;
|
|
2734
1263
|
}
|
|
@@ -2742,18 +1271,6 @@
|
|
|
2742
1271
|
channel.notifyEvent('close', { notify: true });
|
|
2743
1272
|
}
|
|
2744
1273
|
|
|
2745
|
-
/**
|
|
2746
|
-
* Handles CLOSE_CONNECTION_ACKNOWLEDGED action.
|
|
2747
|
-
* Completes close on initiator's side and notifies close event.
|
|
2748
|
-
*
|
|
2749
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
2750
|
-
* @param message - Message event containing the CLOSE_CONNECTION_ACKNOWLEDGED action
|
|
2751
|
-
*
|
|
2752
|
-
* @example Handling close acknowledgment
|
|
2753
|
-
* ```typescript
|
|
2754
|
-
* handleCloseAcknowledged(routingContext, closeAcknowledgedEvent)
|
|
2755
|
-
* ```
|
|
2756
|
-
*/
|
|
2757
1274
|
function handleCloseAcknowledged(context, message) {
|
|
2758
1275
|
const { processManager } = context;
|
|
2759
1276
|
const action = message.data;
|
|
@@ -2766,25 +1283,6 @@
|
|
|
2766
1283
|
channel.notifyEvent('close', { notify: false });
|
|
2767
1284
|
}
|
|
2768
1285
|
|
|
2769
|
-
/**
|
|
2770
|
-
* Handles DENY_CONNECTION action.
|
|
2771
|
-
* Processes connection denial from remote broker.
|
|
2772
|
-
*
|
|
2773
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
2774
|
-
* @param message - Message event containing the DENY_CONNECTION action
|
|
2775
|
-
*
|
|
2776
|
-
* @remarks
|
|
2777
|
-
* Side Effects:
|
|
2778
|
-
* - Terminates the connection process
|
|
2779
|
-
* - Fires 'deny' lifecycle event with error details
|
|
2780
|
-
*
|
|
2781
|
-
* @example Connection denial during handshake
|
|
2782
|
-
* Denial flow (during handshake):
|
|
2783
|
-
* Initiator -> REQUEST_CONNECTION
|
|
2784
|
-
* Responder validates and rejects
|
|
2785
|
-
* Initiator <- DENY_CONNECTION (this handler)
|
|
2786
|
-
* Initiator fires 'deny' event
|
|
2787
|
-
*/
|
|
2788
1286
|
function handleDeny(context, message) {
|
|
2789
1287
|
const { processManager } = context;
|
|
2790
1288
|
const action = message.data;
|
|
@@ -2798,55 +1296,15 @@
|
|
|
2798
1296
|
channel.notifyEvent('deny', { error, origin: message.origin });
|
|
2799
1297
|
}
|
|
2800
1298
|
|
|
2801
|
-
/**
|
|
2802
|
-
* Handles DESTROY_CONNECTION action.
|
|
2803
|
-
* Immediately destroys a connection without handshake.
|
|
2804
|
-
*
|
|
2805
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
2806
|
-
* @param message - Message event containing the DESTROY_CONNECTION action
|
|
2807
|
-
*
|
|
2808
|
-
* @remarks
|
|
2809
|
-
* Side Effects:
|
|
2810
|
-
* - Immediately destroys channel (no acknowledgment)
|
|
2811
|
-
* - Removes channel from registry
|
|
2812
|
-
* - No lifecycle event fired (forceful termination)
|
|
2813
|
-
*
|
|
2814
|
-
* @example Forceful connection termination
|
|
2815
|
-
* Forceful termination (e.g., window unload):
|
|
2816
|
-
* channel.destroy()
|
|
2817
|
-
* -> DESTROY_CONNECTION sent
|
|
2818
|
-
* -> Remote receives (this handler)
|
|
2819
|
-
* -> Channel immediately removed
|
|
2820
|
-
*/
|
|
2821
1299
|
function handleDestroy(context, message) {
|
|
2822
1300
|
const { registry } = context;
|
|
2823
|
-
const
|
|
2824
|
-
const senderId = action.senderId;
|
|
2825
|
-
const channel = getById(registry, senderId);
|
|
1301
|
+
const channel = resolveChannel(registry, message);
|
|
2826
1302
|
if (!channel) {
|
|
2827
1303
|
return;
|
|
2828
1304
|
}
|
|
2829
1305
|
channel.destroy(false);
|
|
2830
1306
|
}
|
|
2831
1307
|
|
|
2832
|
-
/**
|
|
2833
|
-
* Handles INVALID_REQUEST action.
|
|
2834
|
-
* Processes error responses from remote broker.
|
|
2835
|
-
*
|
|
2836
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
2837
|
-
* @param message - Message event containing the INVALID_REQUEST action
|
|
2838
|
-
*
|
|
2839
|
-
* @remarks
|
|
2840
|
-
* Side Effects:
|
|
2841
|
-
* - Fires 'invalid' lifecycle event with error details
|
|
2842
|
-
*
|
|
2843
|
-
* @example Handling protocol violations
|
|
2844
|
-
* Protocol violation detected:
|
|
2845
|
-
* Initiator sends malformed action
|
|
2846
|
-
* Responder detects violation
|
|
2847
|
-
* Initiator <- INVALID_REQUEST (this handler)
|
|
2848
|
-
* Initiator fires 'invalid' event with reason
|
|
2849
|
-
*/
|
|
2850
1308
|
function handleInvalid(context, message) {
|
|
2851
1309
|
const { processManager } = context;
|
|
2852
1310
|
const action = message.data;
|
|
@@ -2890,67 +1348,18 @@
|
|
|
2890
1348
|
additionalProperties: additionalProperties
|
|
2891
1349
|
};
|
|
2892
1350
|
|
|
2893
|
-
/**
|
|
2894
|
-
* Safe copies of JSON built-in methods.
|
|
2895
|
-
*
|
|
2896
|
-
* These references are captured at module initialization time to protect against
|
|
2897
|
-
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
2898
|
-
*
|
|
2899
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/json
|
|
2900
|
-
*/
|
|
2901
1351
|
const _JSON = globalThis.JSON;
|
|
2902
|
-
/**
|
|
2903
|
-
* (Safe copy) Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
|
|
2904
|
-
*/
|
|
2905
1352
|
const stringify = _JSON.stringify;
|
|
2906
1353
|
|
|
2907
|
-
/**
|
|
2908
|
-
* Safe copies of Number built-in methods and constants.
|
|
2909
|
-
*
|
|
2910
|
-
* These references are captured at module initialization time to protect against
|
|
2911
|
-
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
2912
|
-
*
|
|
2913
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/number
|
|
2914
|
-
*/
|
|
2915
1354
|
const _Number = globalThis.Number;
|
|
2916
1355
|
const _parseInt = globalThis.parseInt;
|
|
2917
1356
|
const _isNaN = globalThis.isNaN;
|
|
2918
1357
|
const _isFinite = globalThis.isFinite;
|
|
2919
|
-
/**
|
|
2920
|
-
* (Safe copy) Determines whether the passed value is an integer.
|
|
2921
|
-
*/
|
|
2922
1358
|
const isInteger = _Number.isInteger;
|
|
2923
|
-
/**
|
|
2924
|
-
* (Safe copy) Parses a string and returns an integer.
|
|
2925
|
-
*/
|
|
2926
1359
|
const parseInt = _parseInt;
|
|
2927
|
-
/**
|
|
2928
|
-
* (Safe copy) Global isNaN function (coerces to number first, less strict than Number.isNaN).
|
|
2929
|
-
*/
|
|
2930
1360
|
const globalIsNaN = _isNaN;
|
|
2931
|
-
/**
|
|
2932
|
-
* (Safe copy) Global isFinite function (coerces to number first, less strict than Number.isFinite).
|
|
2933
|
-
*/
|
|
2934
1361
|
const globalIsFinite = _isFinite;
|
|
2935
1362
|
|
|
2936
|
-
/**
|
|
2937
|
-
* Creates a new validation context.
|
|
2938
|
-
*
|
|
2939
|
-
* @param rootSchema - The root schema being validated against
|
|
2940
|
-
* @param validator - The schema validator function
|
|
2941
|
-
* @param collectAllErrors - Whether to collect all errors (default: true)
|
|
2942
|
-
* @param strictPatterns - Whether to report errors for invalid regex patterns (default: false)
|
|
2943
|
-
* @param patternSafetyChecker - Optional pattern safety checker for ReDoS detection
|
|
2944
|
-
* @returns A new validation context
|
|
2945
|
-
* @example Creating validation context
|
|
2946
|
-
* ```typescript
|
|
2947
|
-
* const schema = { type: 'string' }
|
|
2948
|
-
* const ctx = createValidationContext(schema, validateSchema)
|
|
2949
|
-
* // ctx.path === ''
|
|
2950
|
-
* // ctx.errors === []
|
|
2951
|
-
* // ctx.collectAllErrors === true
|
|
2952
|
-
* ```
|
|
2953
|
-
*/
|
|
2954
1363
|
function createValidationContext(rootSchema, validator, collectAllErrors = true, strictPatterns = false, patternSafetyChecker) {
|
|
2955
1364
|
const definitions = createMap();
|
|
2956
1365
|
if (rootSchema.definitions) {
|
|
@@ -2969,21 +1378,6 @@
|
|
|
2969
1378
|
validate: validator,
|
|
2970
1379
|
};
|
|
2971
1380
|
}
|
|
2972
|
-
/**
|
|
2973
|
-
* Creates a child context with updated path.
|
|
2974
|
-
*
|
|
2975
|
-
* @param ctx - Parent context
|
|
2976
|
-
* @param segment - Path segment to append
|
|
2977
|
-
* @returns New context with updated path
|
|
2978
|
-
* @example Creating child context with path
|
|
2979
|
-
* ```typescript
|
|
2980
|
-
* const ctx = createValidationContext(schema, validate)
|
|
2981
|
-
* const childCtx = pushPath(ctx, 'items')
|
|
2982
|
-
* // childCtx.path === '/items'
|
|
2983
|
-
* const nestedCtx = pushPath(childCtx, 0)
|
|
2984
|
-
* // nestedCtx.path === '/items/0'
|
|
2985
|
-
* ```
|
|
2986
|
-
*/
|
|
2987
1381
|
function pushPath(ctx, segment) {
|
|
2988
1382
|
const escapedSegment = String(segment).replace(/~/g, '~0').replace(/\//g, '~1');
|
|
2989
1383
|
return {
|
|
@@ -2991,22 +1385,6 @@
|
|
|
2991
1385
|
path: `${ctx.path}/${escapedSegment}`,
|
|
2992
1386
|
};
|
|
2993
1387
|
}
|
|
2994
|
-
/**
|
|
2995
|
-
* Adds a validation error to the context.
|
|
2996
|
-
*
|
|
2997
|
-
* @param ctx - Validation context
|
|
2998
|
-
* @param message - Human-readable error message
|
|
2999
|
-
* @param instance - The failing value
|
|
3000
|
-
* @param code - Optional error code for programmatic handling
|
|
3001
|
-
* @param params - Optional additional parameters
|
|
3002
|
-
* @example Adding validation error
|
|
3003
|
-
* ```typescript
|
|
3004
|
-
* const ctx = createValidationContext(schema, validate)
|
|
3005
|
-
* addError(ctx, 'Value must be a string', 42, 'type', { expected: 'string' })
|
|
3006
|
-
* // ctx.errors[0].message === 'Value must be a string'
|
|
3007
|
-
* // ctx.errors[0].code === 'type'
|
|
3008
|
-
* ```
|
|
3009
|
-
*/
|
|
3010
1388
|
function addError(ctx, message, instance, code, params) {
|
|
3011
1389
|
ctx.errors.push({
|
|
3012
1390
|
message,
|
|
@@ -3016,42 +1394,10 @@
|
|
|
3016
1394
|
params,
|
|
3017
1395
|
});
|
|
3018
1396
|
}
|
|
3019
|
-
/**
|
|
3020
|
-
* Checks if we should continue validation after an error.
|
|
3021
|
-
*
|
|
3022
|
-
* @param ctx - Validation context
|
|
3023
|
-
* @returns true if we should continue, false if we should stop
|
|
3024
|
-
* @example Checking error collection mode
|
|
3025
|
-
* ```typescript
|
|
3026
|
-
* const ctx = createValidationContext(schema, validate, true) // collectAllErrors: true
|
|
3027
|
-
* addError(ctx, 'First error', 'value', 'error')
|
|
3028
|
-
* shouldContinue(ctx) // => true (keep collecting errors)
|
|
3029
|
-
*
|
|
3030
|
-
* const ctx2 = createValidationContext(schema, validate, false) // collectAllErrors: false
|
|
3031
|
-
* addError(ctx2, 'First error', 'value', 'error')
|
|
3032
|
-
* shouldContinue(ctx2) // => false (stop at first error)
|
|
3033
|
-
* ```
|
|
3034
|
-
*/
|
|
3035
1397
|
function shouldContinue(ctx) {
|
|
3036
1398
|
return ctx.collectAllErrors || ctx.errors.length === 0;
|
|
3037
1399
|
}
|
|
3038
1400
|
|
|
3039
|
-
/**
|
|
3040
|
-
* Performs deep equality check for JSON values.
|
|
3041
|
-
*
|
|
3042
|
-
* Used for enum validation and uniqueItems validation.
|
|
3043
|
-
*
|
|
3044
|
-
* @param a - First value to compare
|
|
3045
|
-
* @param b - Second value to compare
|
|
3046
|
-
* @returns true if values are deeply equal, false otherwise
|
|
3047
|
-
* @example Comparing values for equality
|
|
3048
|
-
* ```typescript
|
|
3049
|
-
* isEqual({ name: 'Alice' }, { name: 'Alice' }) // => true
|
|
3050
|
-
* isEqual([1, 2, 3], [1, 2, 3]) // => true
|
|
3051
|
-
* isEqual({ a: 1 }, { a: 2 }) // => false
|
|
3052
|
-
* isEqual([1, 2], [2, 1]) // => false (order matters)
|
|
3053
|
-
* ```
|
|
3054
|
-
*/
|
|
3055
1401
|
function isEqual(a, b) {
|
|
3056
1402
|
if (a === b)
|
|
3057
1403
|
return true;
|
|
@@ -3086,21 +1432,6 @@
|
|
|
3086
1432
|
return false;
|
|
3087
1433
|
}
|
|
3088
1434
|
|
|
3089
|
-
/**
|
|
3090
|
-
* Validates array length and uniqueItems constraints.
|
|
3091
|
-
*
|
|
3092
|
-
* @param instance - Array being validated
|
|
3093
|
-
* @param schema - Schema containing array bounds
|
|
3094
|
-
* @param ctx - Validation context
|
|
3095
|
-
* @returns true if validation passes, false otherwise
|
|
3096
|
-
* @example Validating array constraints
|
|
3097
|
-
* ```typescript
|
|
3098
|
-
* const schema = { minItems: 2, maxItems: 5, uniqueItems: true }
|
|
3099
|
-
* validateArrayBounds([1, 2, 3], schema, ctx) // => true
|
|
3100
|
-
* validateArrayBounds([1], schema, ctx) // => false (too few items)
|
|
3101
|
-
* validateArrayBounds([1, 1, 2], schema, ctx) // => false (duplicates)
|
|
3102
|
-
* ```
|
|
3103
|
-
*/
|
|
3104
1435
|
function validateArrayBounds(instance, schema, ctx) {
|
|
3105
1436
|
let valid = true;
|
|
3106
1437
|
if (schema.minItems !== undefined && instance.length < schema.minItems) {
|
|
@@ -3136,23 +1467,6 @@
|
|
|
3136
1467
|
return valid;
|
|
3137
1468
|
}
|
|
3138
1469
|
|
|
3139
|
-
/**
|
|
3140
|
-
* Validates 'allOf' keyword - all schemas must match.
|
|
3141
|
-
*
|
|
3142
|
-
* @param instance - Value being validated
|
|
3143
|
-
* @param schema - Schema containing the allOf constraint
|
|
3144
|
-
* @param ctx - Validation context
|
|
3145
|
-
* @returns true if validation passes, false otherwise
|
|
3146
|
-
* @example Validating allOf composition
|
|
3147
|
-
* ```typescript
|
|
3148
|
-
* { type: 'object', required: ['name'] },
|
|
3149
|
-
* { type: 'object', required: ['email'] }
|
|
3150
|
-
* ]
|
|
3151
|
-
* }
|
|
3152
|
-
* validateAllOf({ name: 'Alice', email: 'alice@example.com' }, schema, ctx) // => true
|
|
3153
|
-
* validateAllOf({ name: 'Alice' }, schema, ctx) // => false (missing email)
|
|
3154
|
-
* ```
|
|
3155
|
-
*/
|
|
3156
1470
|
function validateAllOf(instance, schema, ctx) {
|
|
3157
1471
|
const allOf = schema.allOf;
|
|
3158
1472
|
if (!allOf || allOf.length === 0) {
|
|
@@ -3161,7 +1475,6 @@
|
|
|
3161
1475
|
let valid = true;
|
|
3162
1476
|
for (let i = 0; i < allOf.length; i++) {
|
|
3163
1477
|
const subSchema = allOf[i];
|
|
3164
|
-
/* istanbul ignore if -- defensive null check for sparse arrays */
|
|
3165
1478
|
if (!subSchema)
|
|
3166
1479
|
continue;
|
|
3167
1480
|
if (!ctx.validate(instance, subSchema, ctx)) {
|
|
@@ -3172,26 +1485,6 @@
|
|
|
3172
1485
|
}
|
|
3173
1486
|
return valid;
|
|
3174
1487
|
}
|
|
3175
|
-
/**
|
|
3176
|
-
* Validates 'anyOf' keyword - at least one schema must match.
|
|
3177
|
-
*
|
|
3178
|
-
* @param instance - Value being validated
|
|
3179
|
-
* @param schema - Schema containing the anyOf constraint
|
|
3180
|
-
* @param ctx - Validation context
|
|
3181
|
-
* @returns true if validation passes, false otherwise
|
|
3182
|
-
* @example Validating anyOf composition
|
|
3183
|
-
* ```typescript
|
|
3184
|
-
* const schema = {
|
|
3185
|
-
* anyOf: [
|
|
3186
|
-
* { type: 'string' },
|
|
3187
|
-
* { type: 'number' }
|
|
3188
|
-
* ]
|
|
3189
|
-
* }
|
|
3190
|
-
* validateAnyOf('hello', schema, ctx) // => true
|
|
3191
|
-
* validateAnyOf(42, schema, ctx) // => true
|
|
3192
|
-
* validateAnyOf(true, schema, ctx) // => false (neither string nor number)
|
|
3193
|
-
* ```
|
|
3194
|
-
*/
|
|
3195
1488
|
function validateAnyOf(instance, schema, ctx) {
|
|
3196
1489
|
const anyOf = schema.anyOf;
|
|
3197
1490
|
if (!anyOf || anyOf.length === 0) {
|
|
@@ -3207,26 +1500,6 @@
|
|
|
3207
1500
|
addError(ctx, 'Value does not match any of the allowed schemas (anyOf)', instance, 'anyOf');
|
|
3208
1501
|
return false;
|
|
3209
1502
|
}
|
|
3210
|
-
/**
|
|
3211
|
-
* Validates 'oneOf' keyword - exactly one schema must match.
|
|
3212
|
-
*
|
|
3213
|
-
* @param instance - Value being validated
|
|
3214
|
-
* @param schema - Schema containing the oneOf constraint
|
|
3215
|
-
* @param ctx - Validation context
|
|
3216
|
-
* @returns true if validation passes, false otherwise
|
|
3217
|
-
* @example Validating oneOf composition
|
|
3218
|
-
* ```typescript
|
|
3219
|
-
* const schema = {
|
|
3220
|
-
* oneOf: [
|
|
3221
|
-
* { type: 'integer' },
|
|
3222
|
-
* { minimum: 5 }
|
|
3223
|
-
* ]
|
|
3224
|
-
* }
|
|
3225
|
-
* validateOneOf(3, schema, ctx) // => true (matches first only)
|
|
3226
|
-
* validateOneOf(10, schema, ctx) // => false (matches both)
|
|
3227
|
-
* validateOneOf('hi', schema, ctx) // => false (matches neither)
|
|
3228
|
-
* ```
|
|
3229
|
-
*/
|
|
3230
1503
|
function validateOneOf(instance, schema, ctx) {
|
|
3231
1504
|
const oneOf = schema.oneOf;
|
|
3232
1505
|
if (!oneOf || oneOf.length === 0) {
|
|
@@ -3255,20 +1528,6 @@
|
|
|
3255
1528
|
}
|
|
3256
1529
|
return false;
|
|
3257
1530
|
}
|
|
3258
|
-
/**
|
|
3259
|
-
* Validates 'not' keyword - schema must NOT match.
|
|
3260
|
-
*
|
|
3261
|
-
* @param instance - Value being validated
|
|
3262
|
-
* @param schema - Schema containing the not constraint
|
|
3263
|
-
* @param ctx - Validation context
|
|
3264
|
-
* @returns true if validation passes, false otherwise
|
|
3265
|
-
* @example Validating not constraint
|
|
3266
|
-
* ```typescript
|
|
3267
|
-
* const schema = { not: { type: 'string' } }
|
|
3268
|
-
* validateNot(42, schema, ctx) // => true (not a string)
|
|
3269
|
-
* validateNot('hello', schema, ctx) // => false (is a string)
|
|
3270
|
-
* ```
|
|
3271
|
-
*/
|
|
3272
1531
|
function validateNot(instance, schema, ctx) {
|
|
3273
1532
|
const not = schema.not;
|
|
3274
1533
|
if (!not) {
|
|
@@ -3283,58 +1542,31 @@
|
|
|
3283
1542
|
return true;
|
|
3284
1543
|
}
|
|
3285
1544
|
|
|
3286
|
-
/**
|
|
3287
|
-
* Validates object 'dependencies' keyword.
|
|
3288
|
-
*
|
|
3289
|
-
* @param instance - Object being validated
|
|
3290
|
-
* @param schema - Schema containing the dependencies constraint
|
|
3291
|
-
* @param ctx - Validation context
|
|
3292
|
-
* @returns true if validation passes, false otherwise
|
|
3293
|
-
* @example Validating dependent properties
|
|
3294
|
-
* ```typescript
|
|
3295
|
-
* const schema = {
|
|
3296
|
-
* dependencies: {
|
|
3297
|
-
* creditCard: ['billingAddress'], // if creditCard present, billingAddress required
|
|
3298
|
-
* name: { required: ['email'] } // if name present, email required via schema
|
|
3299
|
-
* }
|
|
3300
|
-
* }
|
|
3301
|
-
* validateDependencies({ creditCard: '1234', billingAddress: '123 Main St' }, schema, ctx) // => true
|
|
3302
|
-
* validateDependencies({ creditCard: '1234' }, schema, ctx) // => false (missing billingAddress)
|
|
3303
|
-
* ```
|
|
3304
|
-
*/
|
|
3305
1545
|
function validateDependencies(instance, schema, ctx) {
|
|
3306
1546
|
if (!schema.dependencies) {
|
|
3307
1547
|
return true;
|
|
3308
1548
|
}
|
|
3309
1549
|
let valid = true;
|
|
3310
1550
|
for (const [key, dependency] of entries(schema.dependencies)) {
|
|
3311
|
-
/* istanbul ignore next -- key presence check */
|
|
3312
1551
|
if (!hasOwn(instance, key)) {
|
|
3313
1552
|
continue;
|
|
3314
1553
|
}
|
|
3315
|
-
/* istanbul ignore next -- dependency type check */
|
|
3316
1554
|
if (isArray(dependency)) {
|
|
3317
1555
|
for (const requiredKey of dependency) {
|
|
3318
|
-
/* istanbul ignore next -- required key check */
|
|
3319
1556
|
if (!hasOwn(instance, requiredKey)) {
|
|
3320
1557
|
addError(ctx, `Property '${key}' requires property '${requiredKey}' to also be present`, instance, 'dependencies', {
|
|
3321
1558
|
property: key,
|
|
3322
|
-
/* istanbul ignore next -- required key assignment */
|
|
3323
1559
|
required: requiredKey,
|
|
3324
1560
|
});
|
|
3325
1561
|
valid = false;
|
|
3326
|
-
/* istanbul ignore if -- early exit tested in validate.spec.ts */
|
|
3327
1562
|
if (!shouldContinue(ctx))
|
|
3328
1563
|
return false;
|
|
3329
1564
|
}
|
|
3330
1565
|
}
|
|
3331
1566
|
}
|
|
3332
1567
|
else {
|
|
3333
|
-
/* istanbul ignore next -- schema dependency validation */
|
|
3334
1568
|
if (!ctx.validate(instance, dependency, ctx)) {
|
|
3335
|
-
/* istanbul ignore next -- failure path */
|
|
3336
1569
|
valid = false;
|
|
3337
|
-
/* istanbul ignore if -- early exit tested in validate.spec.ts */
|
|
3338
1570
|
if (!shouldContinue(ctx))
|
|
3339
1571
|
return false;
|
|
3340
1572
|
}
|
|
@@ -3343,20 +1575,6 @@
|
|
|
3343
1575
|
return valid;
|
|
3344
1576
|
}
|
|
3345
1577
|
|
|
3346
|
-
/**
|
|
3347
|
-
* Validates enum constraint.
|
|
3348
|
-
*
|
|
3349
|
-
* @param instance - Value being validated
|
|
3350
|
-
* @param schema - Schema containing the enum constraint
|
|
3351
|
-
* @param ctx - Validation context
|
|
3352
|
-
* @returns true if validation passes, false otherwise
|
|
3353
|
-
* @example Validating enum values
|
|
3354
|
-
* ```typescript
|
|
3355
|
-
* const schema = { enum: ['draft', 'published', 'archived'] }
|
|
3356
|
-
* validateEnum('published', schema, ctx) // => true
|
|
3357
|
-
* validateEnum('deleted', schema, ctx) // => false (not in enum)
|
|
3358
|
-
* ```
|
|
3359
|
-
*/
|
|
3360
1578
|
function validateEnum(instance, schema, ctx) {
|
|
3361
1579
|
if (!schema.enum) {
|
|
3362
1580
|
return true;
|
|
@@ -3373,77 +1591,24 @@
|
|
|
3373
1591
|
return false;
|
|
3374
1592
|
}
|
|
3375
1593
|
|
|
3376
|
-
/**
|
|
3377
|
-
* Safe RegExp factory for protected regex construction.
|
|
3378
|
-
*
|
|
3379
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/regexp
|
|
3380
|
-
*/
|
|
3381
|
-
/* eslint-disable workspace/lib-require-jsdoc-example */
|
|
3382
1594
|
const _RegExp = globalThis.RegExp;
|
|
3383
|
-
const _Reflect$
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
* Use this instead of `new RegExp()`.
|
|
3387
|
-
*
|
|
3388
|
-
* @param pattern - The pattern string or RegExp to copy.
|
|
3389
|
-
* @param flags - Optional flags string.
|
|
3390
|
-
* @returns A new RegExp instance.
|
|
3391
|
-
*/
|
|
3392
|
-
const createRegExp = (pattern, flags) => _Reflect$2.construct(_RegExp, [pattern, flags]);
|
|
3393
|
-
|
|
3394
|
-
/**
|
|
3395
|
-
* Safe copies of URL built-ins via factory functions.
|
|
3396
|
-
*
|
|
3397
|
-
* Provides safe references to URL and URLSearchParams.
|
|
3398
|
-
* These references are captured at module initialization time to protect against
|
|
3399
|
-
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
3400
|
-
*
|
|
3401
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/url
|
|
3402
|
-
*/
|
|
1595
|
+
const _Reflect$3 = globalThis.Reflect;
|
|
1596
|
+
const createRegExp = (pattern, flags) => _Reflect$3.construct(_RegExp, [pattern, flags]);
|
|
1597
|
+
|
|
3403
1598
|
const _URL = globalThis.URL;
|
|
3404
|
-
const _Reflect$
|
|
3405
|
-
|
|
3406
|
-
* (Safe copy) Creates a new URL using the captured URL constructor.
|
|
3407
|
-
* Use this instead of `new URL()`.
|
|
3408
|
-
*
|
|
3409
|
-
* @param url - The URL string to parse.
|
|
3410
|
-
* @param base - Optional base URL for relative URLs.
|
|
3411
|
-
* @returns A new URL instance.
|
|
3412
|
-
*
|
|
3413
|
-
* @example Creating URL instances
|
|
3414
|
-
* ```typescript
|
|
3415
|
-
* const absolute = createURL('https://example.com/path?query=1')
|
|
3416
|
-
* const relative = createURL('/api/users', 'https://example.com')
|
|
3417
|
-
* // => URL { href: 'https://example.com/api/users' }
|
|
3418
|
-
* ```
|
|
3419
|
-
*/
|
|
3420
|
-
const createURL = (url, base) => _Reflect$1.construct(_URL, [url, base]);
|
|
3421
|
-
/**
|
|
3422
|
-
* (Safe copy) Creates an object URL for the given object.
|
|
3423
|
-
* Use this instead of `URL.createObjectURL()`.
|
|
3424
|
-
*
|
|
3425
|
-
* Note: This is a browser-only API. In Node.js environments, this will throw.
|
|
3426
|
-
*/
|
|
1599
|
+
const _Reflect$2 = globalThis.Reflect;
|
|
1600
|
+
const createURL = (url, base) => _Reflect$2.construct(_URL, [url, base]);
|
|
3427
1601
|
typeof _URL.createObjectURL === 'function'
|
|
3428
1602
|
? _URL.createObjectURL.bind(_URL)
|
|
3429
1603
|
: () => {
|
|
3430
1604
|
throw new Error('URL.createObjectURL is not available in this environment');
|
|
3431
1605
|
};
|
|
3432
|
-
/**
|
|
3433
|
-
* (Safe copy) Revokes an object URL previously created with createObjectURL.
|
|
3434
|
-
* Use this instead of `URL.revokeObjectURL()`.
|
|
3435
|
-
*
|
|
3436
|
-
* Note: This is a browser-only API. In Node.js environments, this will throw.
|
|
3437
|
-
*/
|
|
3438
1606
|
typeof _URL.revokeObjectURL === 'function'
|
|
3439
1607
|
? _URL.revokeObjectURL.bind(_URL)
|
|
3440
1608
|
: () => {
|
|
3441
1609
|
throw new Error('URL.revokeObjectURL is not available in this environment');
|
|
3442
1610
|
};
|
|
3443
1611
|
|
|
3444
|
-
/**
|
|
3445
|
-
* Format validators for common string formats.
|
|
3446
|
-
*/
|
|
3447
1612
|
const formatValidators = {
|
|
3448
1613
|
'date-time': (v) => {
|
|
3449
1614
|
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(v))
|
|
@@ -3533,7 +1698,6 @@
|
|
|
3533
1698
|
try {
|
|
3534
1699
|
createURL(v);
|
|
3535
1700
|
return true;
|
|
3536
|
-
/* istanbul ignore next -- URL constructor always throws for invalid URI */
|
|
3537
1701
|
}
|
|
3538
1702
|
catch {
|
|
3539
1703
|
return false;
|
|
@@ -3542,11 +1706,9 @@
|
|
|
3542
1706
|
'uri-reference': (v) => {
|
|
3543
1707
|
try {
|
|
3544
1708
|
createURL(v, 'http://example.com');
|
|
3545
|
-
/* istanbul ignore next -- success path just returns true */
|
|
3546
1709
|
return true;
|
|
3547
1710
|
}
|
|
3548
1711
|
catch {
|
|
3549
|
-
/* istanbul ignore next -- URL constructor is very permissive with base URL */
|
|
3550
1712
|
return false;
|
|
3551
1713
|
}
|
|
3552
1714
|
},
|
|
@@ -3555,7 +1717,6 @@
|
|
|
3555
1717
|
},
|
|
3556
1718
|
regex: (v) => {
|
|
3557
1719
|
try {
|
|
3558
|
-
// eslint-disable-next-line workspace/no-unsafe-regex -- intentionally validating user-provided regex patterns
|
|
3559
1720
|
createRegExp(v);
|
|
3560
1721
|
return true;
|
|
3561
1722
|
}
|
|
@@ -3573,21 +1734,6 @@
|
|
|
3573
1734
|
return segments.every((seg) => validSegment.test(seg));
|
|
3574
1735
|
},
|
|
3575
1736
|
};
|
|
3576
|
-
/**
|
|
3577
|
-
* Validates string format constraint.
|
|
3578
|
-
*
|
|
3579
|
-
* @param instance - String being validated
|
|
3580
|
-
* @param schema - Schema containing the format constraint
|
|
3581
|
-
* @param ctx - Validation context
|
|
3582
|
-
* @returns true if validation passes, false otherwise
|
|
3583
|
-
* @example Validating string formats
|
|
3584
|
-
* ```typescript
|
|
3585
|
-
* validateFormat('user@example.com', { format: 'email' }, ctx) // => true
|
|
3586
|
-
* validateFormat('invalid-email', { format: 'email' }, ctx) // => false
|
|
3587
|
-
* validateFormat('2024-01-15', { format: 'date' }, ctx) // => true
|
|
3588
|
-
* validateFormat('192.168.1.1', { format: 'ipv4' }, ctx) // => true
|
|
3589
|
-
* ```
|
|
3590
|
-
*/
|
|
3591
1737
|
function validateFormat(instance, schema, ctx) {
|
|
3592
1738
|
if (!schema.format) {
|
|
3593
1739
|
return true;
|
|
@@ -3605,27 +1751,6 @@
|
|
|
3605
1751
|
return true;
|
|
3606
1752
|
}
|
|
3607
1753
|
|
|
3608
|
-
/**
|
|
3609
|
-
* Validates array 'items' keyword.
|
|
3610
|
-
*
|
|
3611
|
-
* @param instance - Array being validated
|
|
3612
|
-
* @param schema - Schema containing the items constraint
|
|
3613
|
-
* @param ctx - Validation context
|
|
3614
|
-
* @returns true if validation passes, false otherwise
|
|
3615
|
-
* @example Single schema for all items
|
|
3616
|
-
* ```typescript
|
|
3617
|
-
* const schema = { items: { type: 'string' } }
|
|
3618
|
-
* validateItems(['a', 'b', 'c'], schema, ctx) // => true
|
|
3619
|
-
* validateItems(['a', 1, 'c'], schema, ctx) // => false (1 is not a string)
|
|
3620
|
-
* ```
|
|
3621
|
-
*
|
|
3622
|
-
* @example Tuple validation
|
|
3623
|
-
* ```typescript
|
|
3624
|
-
* const schema = { items: [{ type: 'string' }, { type: 'number' }] }
|
|
3625
|
-
* validateItems(['name', 42], schema, ctx) // => true
|
|
3626
|
-
* validateItems([42, 'name'], schema, ctx) // => false (wrong types)
|
|
3627
|
-
* ```
|
|
3628
|
-
*/
|
|
3629
1754
|
function validateItems(instance, schema, ctx) {
|
|
3630
1755
|
const items = schema.items;
|
|
3631
1756
|
if (items === undefined) {
|
|
@@ -3635,13 +1760,11 @@
|
|
|
3635
1760
|
if (isArray(items)) {
|
|
3636
1761
|
for (let i = 0; i < items.length && i < instance.length; i++) {
|
|
3637
1762
|
const itemSchema = items[i];
|
|
3638
|
-
/* istanbul ignore if -- defensive null check for sparse arrays */
|
|
3639
1763
|
if (!itemSchema)
|
|
3640
1764
|
continue;
|
|
3641
1765
|
const itemCtx = pushPath(ctx, i);
|
|
3642
1766
|
if (!ctx.validate(instance[i], itemSchema, itemCtx)) {
|
|
3643
1767
|
valid = false;
|
|
3644
|
-
/* istanbul ignore if -- early exit already tested in validate.spec.ts */
|
|
3645
1768
|
if (!shouldContinue(ctx))
|
|
3646
1769
|
return false;
|
|
3647
1770
|
}
|
|
@@ -3664,24 +1787,12 @@
|
|
|
3664
1787
|
}
|
|
3665
1788
|
return valid;
|
|
3666
1789
|
}
|
|
3667
|
-
/**
|
|
3668
|
-
* Validates 'additionalItems' keyword for tuple arrays.
|
|
3669
|
-
*
|
|
3670
|
-
* @param instance - Array being validated
|
|
3671
|
-
* @param schema - Schema containing the additionalItems constraint
|
|
3672
|
-
* @param ctx - Validation context
|
|
3673
|
-
* @param startIndex - Index from which to start checking additional items
|
|
3674
|
-
* @returns true if validation passes, false otherwise
|
|
3675
|
-
*/
|
|
3676
1790
|
function validateAdditionalItems(instance, schema, ctx, startIndex) {
|
|
3677
1791
|
const additionalItems = schema.additionalItems;
|
|
3678
|
-
/* istanbul ignore if -- default case handled in items.spec.ts */
|
|
3679
1792
|
if (additionalItems === undefined) {
|
|
3680
1793
|
return true;
|
|
3681
1794
|
}
|
|
3682
|
-
/* istanbul ignore next -- additionalItems initialization and branching */
|
|
3683
1795
|
let valid = true;
|
|
3684
|
-
/* istanbul ignore next -- additionalItems branching */
|
|
3685
1796
|
if (additionalItems === false) {
|
|
3686
1797
|
if (instance.length > startIndex) {
|
|
3687
1798
|
addError(ctx, `Array has too many items. Expected at most ${startIndex}, got ${instance.length}`, instance, 'additionalItems', {
|
|
@@ -3694,10 +1805,8 @@
|
|
|
3694
1805
|
else if (typeof additionalItems === 'object') {
|
|
3695
1806
|
for (let i = startIndex; i < instance.length; i++) {
|
|
3696
1807
|
const itemCtx = pushPath(ctx, i);
|
|
3697
|
-
/* istanbul ignore else -- validation failure tested in items.spec.ts */
|
|
3698
1808
|
if (!ctx.validate(instance[i], additionalItems, itemCtx)) {
|
|
3699
1809
|
valid = false;
|
|
3700
|
-
/* istanbul ignore if -- early exit tested in validate.spec.ts */
|
|
3701
1810
|
if (!shouldContinue(ctx))
|
|
3702
1811
|
return false;
|
|
3703
1812
|
}
|
|
@@ -3706,22 +1815,6 @@
|
|
|
3706
1815
|
return valid;
|
|
3707
1816
|
}
|
|
3708
1817
|
|
|
3709
|
-
/**
|
|
3710
|
-
* Validates number range and multipleOf constraints.
|
|
3711
|
-
*
|
|
3712
|
-
* @param instance - Number being validated
|
|
3713
|
-
* @param schema - Schema containing number constraints
|
|
3714
|
-
* @param ctx - Validation context
|
|
3715
|
-
* @returns true if validation passes, false otherwise
|
|
3716
|
-
* @example Validating number constraints
|
|
3717
|
-
* ```typescript
|
|
3718
|
-
* const schema = { minimum: 0, maximum: 100, multipleOf: 5 }
|
|
3719
|
-
* validateNumberBounds(50, schema, ctx) // => true
|
|
3720
|
-
* validateNumberBounds(-1, schema, ctx) // => false (below minimum)
|
|
3721
|
-
* validateNumberBounds(101, schema, ctx) // => false (above maximum)
|
|
3722
|
-
* validateNumberBounds(17, schema, ctx) // => false (not multiple of 5)
|
|
3723
|
-
* ```
|
|
3724
|
-
*/
|
|
3725
1818
|
function validateNumberBounds(instance, schema, ctx) {
|
|
3726
1819
|
let valid = true;
|
|
3727
1820
|
if (schema.minimum !== undefined) {
|
|
@@ -3768,21 +1861,6 @@
|
|
|
3768
1861
|
return valid;
|
|
3769
1862
|
}
|
|
3770
1863
|
|
|
3771
|
-
/**
|
|
3772
|
-
* Validates object bounds constraints (minProperties, maxProperties).
|
|
3773
|
-
*
|
|
3774
|
-
* @param instance - Object being validated
|
|
3775
|
-
* @param schema - Schema containing object bounds
|
|
3776
|
-
* @param ctx - Validation context
|
|
3777
|
-
* @returns true if validation passes, false otherwise
|
|
3778
|
-
* @example Validating object property counts
|
|
3779
|
-
* ```typescript
|
|
3780
|
-
* const schema = { minProperties: 1, maxProperties: 3 }
|
|
3781
|
-
* validateObjectBounds({ a: 1, b: 2 }, schema, ctx) // => true
|
|
3782
|
-
* validateObjectBounds({}, schema, ctx) // => false (no properties)
|
|
3783
|
-
* validateObjectBounds({ a: 1, b: 2, c: 3, d: 4 }, schema, ctx) // => false (too many)
|
|
3784
|
-
* ```
|
|
3785
|
-
*/
|
|
3786
1864
|
function validateObjectBounds(instance, schema, ctx) {
|
|
3787
1865
|
let valid = true;
|
|
3788
1866
|
const propertyCount = keys(instance).length;
|
|
@@ -3807,25 +1885,6 @@
|
|
|
3807
1885
|
return valid;
|
|
3808
1886
|
}
|
|
3809
1887
|
|
|
3810
|
-
/**
|
|
3811
|
-
* Validates object 'patternProperties' keyword.
|
|
3812
|
-
*
|
|
3813
|
-
* @param instance - Object being validated
|
|
3814
|
-
* @param schema - Schema containing the patternProperties constraint
|
|
3815
|
-
* @param ctx - Validation context
|
|
3816
|
-
* @returns true if validation passes, false otherwise
|
|
3817
|
-
* @example Validating pattern properties
|
|
3818
|
-
* ```typescript
|
|
3819
|
-
* const schema = {
|
|
3820
|
-
* patternProperties: {
|
|
3821
|
-
* '^x-': { type: 'string' }, // extension properties must be strings
|
|
3822
|
-
* '^\\d+$': { type: 'number' } // numeric keys must have number values
|
|
3823
|
-
* }
|
|
3824
|
-
* }
|
|
3825
|
-
* validatePatternProperties({ 'x-custom': 'value', '42': 100 }, schema, ctx) // => true
|
|
3826
|
-
* validatePatternProperties({ 'x-custom': 123 }, schema, ctx) // => false (should be string)
|
|
3827
|
-
* ```
|
|
3828
|
-
*/
|
|
3829
1888
|
function validatePatternProperties(instance, schema, ctx) {
|
|
3830
1889
|
if (!schema.patternProperties) {
|
|
3831
1890
|
return true;
|
|
@@ -3833,7 +1892,6 @@
|
|
|
3833
1892
|
let valid = true;
|
|
3834
1893
|
const patterns = [];
|
|
3835
1894
|
for (const [pattern, patternSchema] of entries(schema.patternProperties)) {
|
|
3836
|
-
/* istanbul ignore if -- patternSafetyChecker branch tested in validate.spec.ts */
|
|
3837
1895
|
if (ctx.patternSafetyChecker) {
|
|
3838
1896
|
const safetyResult = ctx.patternSafetyChecker(pattern);
|
|
3839
1897
|
if (!safetyResult.safe) {
|
|
@@ -3848,21 +1906,15 @@
|
|
|
3848
1906
|
}
|
|
3849
1907
|
}
|
|
3850
1908
|
try {
|
|
3851
|
-
// eslint-disable-next-line workspace/no-unsafe-regex -- Pattern safety validated above when safePatterns enabled
|
|
3852
1909
|
patterns.push({ regex: createRegExp(pattern), schema: patternSchema });
|
|
3853
1910
|
}
|
|
3854
1911
|
catch (e) {
|
|
3855
|
-
/* istanbul ignore next -- strictPatterns mode verified in validate.spec.ts */
|
|
3856
1912
|
if (ctx.strictPatterns) {
|
|
3857
|
-
/* istanbul ignore next -- error reporting for invalid regex */
|
|
3858
1913
|
addError(ctx, `Invalid regex pattern in patternProperties: ${pattern}`, instance, 'patternProperties', {
|
|
3859
|
-
/* istanbul ignore next -- error message extraction */
|
|
3860
1914
|
pattern,
|
|
3861
|
-
/* istanbul ignore next -- ternary expression */
|
|
3862
1915
|
error: e instanceof Error ? e.message : 'Invalid regex',
|
|
3863
1916
|
});
|
|
3864
1917
|
valid = false;
|
|
3865
|
-
/* istanbul ignore if -- early exit tested in validate.spec.ts */
|
|
3866
1918
|
if (!shouldContinue(ctx))
|
|
3867
1919
|
return false;
|
|
3868
1920
|
}
|
|
@@ -3883,25 +1935,6 @@
|
|
|
3883
1935
|
return valid;
|
|
3884
1936
|
}
|
|
3885
1937
|
|
|
3886
|
-
/**
|
|
3887
|
-
* Validates object 'properties' keyword.
|
|
3888
|
-
*
|
|
3889
|
-
* @param instance - Object being validated
|
|
3890
|
-
* @param schema - Schema containing the properties constraint
|
|
3891
|
-
* @param ctx - Validation context
|
|
3892
|
-
* @returns true if validation passes, false otherwise
|
|
3893
|
-
* @example Validating object properties
|
|
3894
|
-
* ```typescript
|
|
3895
|
-
* const schema = {
|
|
3896
|
-
* properties: {
|
|
3897
|
-
* name: { type: 'string' },
|
|
3898
|
-
* age: { type: 'integer' }
|
|
3899
|
-
* }
|
|
3900
|
-
* }
|
|
3901
|
-
* validateProperties({ name: 'Alice', age: 30 }, schema, ctx) // => true
|
|
3902
|
-
* validateProperties({ name: 123 }, schema, ctx) // => false (name should be string)
|
|
3903
|
-
* ```
|
|
3904
|
-
*/
|
|
3905
1938
|
function validateProperties(instance, schema, ctx) {
|
|
3906
1939
|
if (!schema.properties) {
|
|
3907
1940
|
return true;
|
|
@@ -3919,20 +1952,6 @@
|
|
|
3919
1952
|
}
|
|
3920
1953
|
return valid;
|
|
3921
1954
|
}
|
|
3922
|
-
/**
|
|
3923
|
-
* Validates object 'required' keyword.
|
|
3924
|
-
*
|
|
3925
|
-
* @param instance - Object being validated
|
|
3926
|
-
* @param schema - Schema containing the required constraint
|
|
3927
|
-
* @param ctx - Validation context
|
|
3928
|
-
* @returns true if validation passes, false otherwise
|
|
3929
|
-
* @example Validating required properties
|
|
3930
|
-
* ```typescript
|
|
3931
|
-
* const schema = { required: ['name', 'email'] }
|
|
3932
|
-
* validateRequired({ name: 'Alice', email: 'alice@example.com' }, schema, ctx) // => true
|
|
3933
|
-
* validateRequired({ name: 'Alice' }, schema, ctx) // => false (missing email)
|
|
3934
|
-
* ```
|
|
3935
|
-
*/
|
|
3936
1955
|
function validateRequired(instance, schema, ctx) {
|
|
3937
1956
|
if (!schema.required) {
|
|
3938
1957
|
return true;
|
|
@@ -3948,38 +1967,18 @@
|
|
|
3948
1967
|
}
|
|
3949
1968
|
return valid;
|
|
3950
1969
|
}
|
|
3951
|
-
/**
|
|
3952
|
-
* Validates object 'additionalProperties' keyword.
|
|
3953
|
-
*
|
|
3954
|
-
* @param instance - Object being validated
|
|
3955
|
-
* @param schema - Schema containing the additionalProperties constraint
|
|
3956
|
-
* @param ctx - Validation context
|
|
3957
|
-
* @returns true if validation passes, false otherwise
|
|
3958
|
-
* @example Validating additional properties
|
|
3959
|
-
* ```typescript
|
|
3960
|
-
* const schema = {
|
|
3961
|
-
* properties: { name: { type: 'string' } },
|
|
3962
|
-
* additionalProperties: false
|
|
3963
|
-
* }
|
|
3964
|
-
* validateAdditionalProperties({ name: 'Alice' }, schema, ctx) // => true
|
|
3965
|
-
* validateAdditionalProperties({ name: 'Alice', extra: 1 }, schema, ctx) // => false
|
|
3966
|
-
* ```
|
|
3967
|
-
*/
|
|
3968
1970
|
function validateAdditionalProperties(instance, schema, ctx) {
|
|
3969
1971
|
const additionalProperties = schema.additionalProperties;
|
|
3970
1972
|
if (additionalProperties === undefined) {
|
|
3971
1973
|
return true;
|
|
3972
1974
|
}
|
|
3973
|
-
/* istanbul ignore next -- definedKeys initialization */
|
|
3974
1975
|
const definedKeys = createSet();
|
|
3975
|
-
/* istanbul ignore next -- schema.properties may not exist */
|
|
3976
1976
|
if (schema.properties) {
|
|
3977
1977
|
for (const key of keys(schema.properties)) {
|
|
3978
1978
|
definedKeys.add(key);
|
|
3979
1979
|
}
|
|
3980
1980
|
}
|
|
3981
1981
|
const patterns = [];
|
|
3982
|
-
/* istanbul ignore next -- patternProperties may not always be present */
|
|
3983
1982
|
if (schema.patternProperties) {
|
|
3984
1983
|
for (const pattern of keys(schema.patternProperties)) {
|
|
3985
1984
|
if (ctx.patternSafetyChecker) {
|
|
@@ -3989,12 +1988,9 @@
|
|
|
3989
1988
|
}
|
|
3990
1989
|
}
|
|
3991
1990
|
try {
|
|
3992
|
-
// eslint-disable-next-line workspace/no-unsafe-regex -- Pattern safety validated above when safePatterns enabled
|
|
3993
1991
|
patterns.push(createRegExp(pattern));
|
|
3994
|
-
/* istanbul ignore next -- invalid regex patterns handled in patternProperties validator */
|
|
3995
1992
|
}
|
|
3996
1993
|
catch {
|
|
3997
|
-
// Invalid regex, skip
|
|
3998
1994
|
}
|
|
3999
1995
|
}
|
|
4000
1996
|
}
|
|
@@ -4011,7 +2007,6 @@
|
|
|
4011
2007
|
property: key,
|
|
4012
2008
|
});
|
|
4013
2009
|
valid = false;
|
|
4014
|
-
/* istanbul ignore if -- early exit tested in validate.spec.ts */
|
|
4015
2010
|
if (!shouldContinue(ctx))
|
|
4016
2011
|
return false;
|
|
4017
2012
|
}
|
|
@@ -4019,7 +2014,6 @@
|
|
|
4019
2014
|
const propCtx = pushPath(ctx, key);
|
|
4020
2015
|
if (!ctx.validate(instance[key], additionalProperties, propCtx)) {
|
|
4021
2016
|
valid = false;
|
|
4022
|
-
/* istanbul ignore if -- early exit tested in validate.spec.ts */
|
|
4023
2017
|
if (!shouldContinue(ctx))
|
|
4024
2018
|
return false;
|
|
4025
2019
|
}
|
|
@@ -4028,21 +2022,6 @@
|
|
|
4028
2022
|
return valid;
|
|
4029
2023
|
}
|
|
4030
2024
|
|
|
4031
|
-
/**
|
|
4032
|
-
* Validates string length and pattern constraints.
|
|
4033
|
-
*
|
|
4034
|
-
* @param instance - String being validated
|
|
4035
|
-
* @param schema - Schema containing string constraints
|
|
4036
|
-
* @param ctx - Validation context
|
|
4037
|
-
* @returns true if validation passes, false otherwise
|
|
4038
|
-
* @example Validating string constraints
|
|
4039
|
-
* ```typescript
|
|
4040
|
-
* const schema = { minLength: 3, maxLength: 10, pattern: '^[a-z]+$' }
|
|
4041
|
-
* validateStringBounds('hello', schema, ctx) // => true
|
|
4042
|
-
* validateStringBounds('hi', schema, ctx) // => false (too short)
|
|
4043
|
-
* validateStringBounds('Hello', schema, ctx) // => false (contains uppercase)
|
|
4044
|
-
* ```
|
|
4045
|
-
*/
|
|
4046
2025
|
function validateStringBounds(instance, schema, ctx) {
|
|
4047
2026
|
let valid = true;
|
|
4048
2027
|
if (schema.minLength !== undefined && instance.length < schema.minLength) {
|
|
@@ -4078,7 +2057,6 @@
|
|
|
4078
2057
|
}
|
|
4079
2058
|
}
|
|
4080
2059
|
try {
|
|
4081
|
-
// eslint-disable-next-line workspace/no-unsafe-regex -- Pattern safety validated above when safePatterns enabled
|
|
4082
2060
|
const regex = createRegExp(schema.pattern);
|
|
4083
2061
|
if (!regex.test(instance)) {
|
|
4084
2062
|
addError(ctx, `String does not match pattern: ${schema.pattern}`, instance, 'pattern', {
|
|
@@ -4090,16 +2068,12 @@
|
|
|
4090
2068
|
}
|
|
4091
2069
|
}
|
|
4092
2070
|
catch (e) {
|
|
4093
|
-
/* istanbul ignore next -- strictPatterns mode verified in validate.spec.ts */
|
|
4094
2071
|
if (ctx.strictPatterns) {
|
|
4095
|
-
/* istanbul ignore next -- error reporting for invalid regex */
|
|
4096
2072
|
addError(ctx, `Invalid regex pattern: ${schema.pattern}`, instance, 'pattern', {
|
|
4097
2073
|
pattern: schema.pattern,
|
|
4098
|
-
/* istanbul ignore next -- error message extraction ternary */
|
|
4099
2074
|
error: e instanceof Error ? e.message : 'Invalid regex',
|
|
4100
2075
|
});
|
|
4101
2076
|
valid = false;
|
|
4102
|
-
/* istanbul ignore if -- early exit tested in validate.spec.ts */
|
|
4103
2077
|
if (!shouldContinue(ctx))
|
|
4104
2078
|
return false;
|
|
4105
2079
|
}
|
|
@@ -4108,9 +2082,6 @@
|
|
|
4108
2082
|
return valid;
|
|
4109
2083
|
}
|
|
4110
2084
|
|
|
4111
|
-
/**
|
|
4112
|
-
* Type checking functions for JSON Schema types.
|
|
4113
|
-
*/
|
|
4114
2085
|
const typeCheckers = {
|
|
4115
2086
|
string: (v) => typeof v === 'string',
|
|
4116
2087
|
number: (v) => typeof v === 'number' && globalIsFinite(v),
|
|
@@ -4120,12 +2091,6 @@
|
|
|
4120
2091
|
object: (v) => v !== null && typeof v === 'object' && !isArray(v),
|
|
4121
2092
|
null: (v) => v === null,
|
|
4122
2093
|
};
|
|
4123
|
-
/**
|
|
4124
|
-
* Gets the actual JSON type of a value for error messages.
|
|
4125
|
-
*
|
|
4126
|
-
* @param value - The value to get the type of
|
|
4127
|
-
* @returns The JSON type as a string
|
|
4128
|
-
*/
|
|
4129
2094
|
function getActualType(value) {
|
|
4130
2095
|
if (value === null)
|
|
4131
2096
|
return 'null';
|
|
@@ -4134,28 +2099,12 @@
|
|
|
4134
2099
|
const t = typeof value;
|
|
4135
2100
|
if (t === 'number') {
|
|
4136
2101
|
const num = value;
|
|
4137
|
-
/* istanbul ignore next -- NaN/Infinity edge case */
|
|
4138
2102
|
if (!globalIsFinite(num))
|
|
4139
2103
|
return 'number';
|
|
4140
2104
|
return isInteger(num) ? 'integer' : 'number';
|
|
4141
2105
|
}
|
|
4142
2106
|
return t;
|
|
4143
2107
|
}
|
|
4144
|
-
/**
|
|
4145
|
-
* Validates the 'type' keyword.
|
|
4146
|
-
*
|
|
4147
|
-
* @param instance - Value being validated
|
|
4148
|
-
* @param schema - Schema containing the type constraint
|
|
4149
|
-
* @param ctx - Validation context
|
|
4150
|
-
* @returns true if validation passes, false otherwise
|
|
4151
|
-
* @example Validating type constraints
|
|
4152
|
-
* ```typescript
|
|
4153
|
-
* validateType('hello', { type: 'string' }, ctx) // => true
|
|
4154
|
-
* validateType(42, { type: 'integer' }, ctx) // => true
|
|
4155
|
-
* validateType('42', { type: 'integer' }, ctx) // => false
|
|
4156
|
-
* validateType(null, { type: ['string', 'null'] }, ctx) // => true (union type)
|
|
4157
|
-
* ```
|
|
4158
|
-
*/
|
|
4159
2108
|
function validateType(instance, schema, ctx) {
|
|
4160
2109
|
const schemaType = schema.type;
|
|
4161
2110
|
if (schemaType === undefined) {
|
|
@@ -4164,11 +2113,9 @@
|
|
|
4164
2113
|
const types = isArray(schemaType) ? schemaType : [schemaType];
|
|
4165
2114
|
for (const type of types) {
|
|
4166
2115
|
const checker = typeCheckers[type];
|
|
4167
|
-
/* istanbul ignore if -- defensive check for unknown type */
|
|
4168
2116
|
if (checker && checker(instance)) {
|
|
4169
2117
|
return true;
|
|
4170
2118
|
}
|
|
4171
|
-
/* istanbul ignore if -- defensive fallback for integer/number coercion */
|
|
4172
2119
|
if (type === 'number' && typeCheckers['integer']?.(instance)) {
|
|
4173
2120
|
return true;
|
|
4174
2121
|
}
|
|
@@ -4182,24 +2129,6 @@
|
|
|
4182
2129
|
return false;
|
|
4183
2130
|
}
|
|
4184
2131
|
|
|
4185
|
-
/**
|
|
4186
|
-
* Resolves a $ref JSON Pointer to its target schema.
|
|
4187
|
-
*
|
|
4188
|
-
* @param ref - The $ref string (e.g., '#/definitions/Address')
|
|
4189
|
-
* @param ctx - Validation context containing root schema and definitions
|
|
4190
|
-
* @returns The resolved schema, or undefined if not found
|
|
4191
|
-
* @example Resolving schema references
|
|
4192
|
-
* ```typescript
|
|
4193
|
-
* const rootSchema = {
|
|
4194
|
-
* definitions: {
|
|
4195
|
-
* Address: { type: 'object', properties: { street: { type: 'string' } } }
|
|
4196
|
-
* }
|
|
4197
|
-
* }
|
|
4198
|
-
* const ctx = createValidationContext(rootSchema, validate)
|
|
4199
|
-
* resolveRef('#/definitions/Address', ctx)
|
|
4200
|
-
* // => { type: 'object', properties: { street: { type: 'string' } } }
|
|
4201
|
-
* ```
|
|
4202
|
-
*/
|
|
4203
2132
|
function resolveRef(ref, ctx) {
|
|
4204
2133
|
const cached = ctx.definitions.get(ref);
|
|
4205
2134
|
if (cached) {
|
|
@@ -4234,21 +2163,6 @@
|
|
|
4234
2163
|
return undefined;
|
|
4235
2164
|
}
|
|
4236
2165
|
|
|
4237
|
-
/**
|
|
4238
|
-
* Validates a value against a JSON Schema.
|
|
4239
|
-
*
|
|
4240
|
-
* @param instance - The value to validate
|
|
4241
|
-
* @param schema - The JSON Schema to validate against
|
|
4242
|
-
* @param options - Validation options
|
|
4243
|
-
* @returns Validation result with valid flag and any errors
|
|
4244
|
-
*
|
|
4245
|
-
* @example Basic validation
|
|
4246
|
-
* ```typescript
|
|
4247
|
-
* const schema = { type: 'string', minLength: 1 }
|
|
4248
|
-
* const result = validate('hello', schema)
|
|
4249
|
-
* console.log(result.valid) // true
|
|
4250
|
-
* ```
|
|
4251
|
-
*/
|
|
4252
2166
|
function validate(instance, schema, options) {
|
|
4253
2167
|
let patternSafetyChecker;
|
|
4254
2168
|
const ctx = createValidationContext(schema, validateSchema, true, false, patternSafetyChecker);
|
|
@@ -4258,25 +2172,9 @@
|
|
|
4258
2172
|
errors: ctx.errors,
|
|
4259
2173
|
};
|
|
4260
2174
|
}
|
|
4261
|
-
/**
|
|
4262
|
-
* Internal recursive validation function.
|
|
4263
|
-
*
|
|
4264
|
-
* @param instance - Value being validated
|
|
4265
|
-
* @param schema - Schema to validate against
|
|
4266
|
-
* @param ctx - Validation context
|
|
4267
|
-
* @returns true if validation passes, false otherwise
|
|
4268
|
-
* @example Internal recursive validation
|
|
4269
|
-
* ```typescript
|
|
4270
|
-
* const schema = { type: 'object', properties: { count: { type: 'integer' } } }
|
|
4271
|
-
* const ctx = createValidationContext(schema, validateSchema)
|
|
4272
|
-
* validateSchema({ count: 5 }, schema, ctx) // => true
|
|
4273
|
-
* validateSchema({ count: 'five' }, schema, ctx) // => false
|
|
4274
|
-
* ```
|
|
4275
|
-
*/
|
|
4276
2175
|
function validateSchema(instance, schema, ctx) {
|
|
4277
2176
|
if (schema.$ref) {
|
|
4278
2177
|
const resolved = resolveRef(schema.$ref, ctx);
|
|
4279
|
-
/* istanbul ignore if -- $ref resolution failures are tested in resolve-ref.spec.ts */
|
|
4280
2178
|
if (!resolved) {
|
|
4281
2179
|
return true;
|
|
4282
2180
|
}
|
|
@@ -4333,35 +2231,26 @@
|
|
|
4333
2231
|
}
|
|
4334
2232
|
if (!validateRequired(obj, schema, ctx)) {
|
|
4335
2233
|
valid = false;
|
|
4336
|
-
/* istanbul ignore if -- early exit tested elsewhere */
|
|
4337
2234
|
if (!shouldContinue(ctx))
|
|
4338
2235
|
return false;
|
|
4339
2236
|
}
|
|
4340
|
-
/* istanbul ignore next -- patternProperties validation */
|
|
4341
2237
|
if (!validatePatternProperties(obj, schema, ctx)) {
|
|
4342
2238
|
valid = false;
|
|
4343
|
-
/* istanbul ignore next -- early exit tested elsewhere */
|
|
4344
2239
|
if (!shouldContinue(ctx))
|
|
4345
2240
|
return false;
|
|
4346
2241
|
}
|
|
4347
|
-
/* istanbul ignore next -- additionalProperties validation */
|
|
4348
2242
|
if (!validateAdditionalProperties(obj, schema, ctx)) {
|
|
4349
2243
|
valid = false;
|
|
4350
|
-
/* istanbul ignore next -- early exit tested elsewhere */
|
|
4351
2244
|
if (!shouldContinue(ctx))
|
|
4352
2245
|
return false;
|
|
4353
2246
|
}
|
|
4354
|
-
/* istanbul ignore next -- objectBounds validation */
|
|
4355
2247
|
if (!validateObjectBounds(obj, schema, ctx)) {
|
|
4356
2248
|
valid = false;
|
|
4357
|
-
/* istanbul ignore next -- early exit tested elsewhere */
|
|
4358
2249
|
if (!shouldContinue(ctx))
|
|
4359
2250
|
return false;
|
|
4360
2251
|
}
|
|
4361
|
-
/* istanbul ignore next -- dependencies validation */
|
|
4362
2252
|
if (!validateDependencies(obj, schema, ctx)) {
|
|
4363
2253
|
valid = false;
|
|
4364
|
-
/* istanbul ignore next -- early exit tested elsewhere */
|
|
4365
2254
|
if (!shouldContinue(ctx))
|
|
4366
2255
|
return false;
|
|
4367
2256
|
}
|
|
@@ -4389,51 +2278,10 @@
|
|
|
4389
2278
|
return valid;
|
|
4390
2279
|
}
|
|
4391
2280
|
|
|
4392
|
-
/**
|
|
4393
|
-
* Creates a reusable validator function from a JSON Schema.
|
|
4394
|
-
*
|
|
4395
|
-
* @param schema - JSON Schema to validate against
|
|
4396
|
-
* @param options - Validation options
|
|
4397
|
-
* @returns A function that validates data against the schema
|
|
4398
|
-
*
|
|
4399
|
-
* @example Creating reusable validator
|
|
4400
|
-
* ```typescript
|
|
4401
|
-
* const schema = {
|
|
4402
|
-
* type: 'object',
|
|
4403
|
-
* properties: {
|
|
4404
|
-
* name: { type: 'string' },
|
|
4405
|
-
* age: { type: 'integer', minimum: 0 }
|
|
4406
|
-
* },
|
|
4407
|
-
* required: ['name']
|
|
4408
|
-
* }
|
|
4409
|
-
*
|
|
4410
|
-
* const validateUser = createValidator(schema)
|
|
4411
|
-
* const result = validateUser({ name: 'Alice', age: 30 })
|
|
4412
|
-
* console.log(result.valid) // true
|
|
4413
|
-
* ```
|
|
4414
|
-
*/
|
|
4415
2281
|
function createValidator$1(schema, options) {
|
|
4416
2282
|
return (data) => validate(data, schema);
|
|
4417
2283
|
}
|
|
4418
2284
|
|
|
4419
|
-
/**
|
|
4420
|
-
* Creates a validator function from a JSON schema.
|
|
4421
|
-
* Returns a function that validates data against the schema.
|
|
4422
|
-
*
|
|
4423
|
-
* @param schema - JSON Schema to validate against
|
|
4424
|
-
* @returns Validator function that returns validation results
|
|
4425
|
-
*
|
|
4426
|
-
* @example Creating a schema validator
|
|
4427
|
-
* ```typescript
|
|
4428
|
-
* const validateUser = createValidator({
|
|
4429
|
-
* type: 'object',
|
|
4430
|
-
* properties: { name: { type: 'string' } },
|
|
4431
|
-
* required: ['name']
|
|
4432
|
-
* })
|
|
4433
|
-
* const result = validateUser({ name: 'Alice' })
|
|
4434
|
-
* // => { valid: true, errors: [] }
|
|
4435
|
-
* ```
|
|
4436
|
-
*/
|
|
4437
2285
|
function createValidator(schema) {
|
|
4438
2286
|
const validator = createValidator$1(schema);
|
|
4439
2287
|
return (data) => {
|
|
@@ -4449,47 +2297,19 @@
|
|
|
4449
2297
|
};
|
|
4450
2298
|
}
|
|
4451
2299
|
|
|
4452
|
-
/* istanbul ignore next -- validator initialization happens at module load */
|
|
4453
2300
|
const validateMessageData = createValidator(messageSchema);
|
|
4454
|
-
/**
|
|
4455
|
-
* Validates a user message against the message schema.
|
|
4456
|
-
*
|
|
4457
|
-
* @param message - The message to validate
|
|
4458
|
-
* @returns Validation result with any errors
|
|
4459
|
-
*/
|
|
4460
2301
|
function validateMessage(message) {
|
|
4461
2302
|
return validateMessageData(message);
|
|
4462
2303
|
}
|
|
4463
2304
|
|
|
4464
|
-
/**
|
|
4465
|
-
* Handles NEW_MESSAGE action.
|
|
4466
|
-
* Routes messages to appropriate channel.
|
|
4467
|
-
*
|
|
4468
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
4469
|
-
* @param message - Message event containing the NEW_MESSAGE action
|
|
4470
|
-
*
|
|
4471
|
-
* @remarks
|
|
4472
|
-
* Side Effects:
|
|
4473
|
-
* - Validates message type against contract
|
|
4474
|
-
* - Invokes channel message handlers if validation passes
|
|
4475
|
-
* - Logs and ignores invalid messages
|
|
4476
|
-
*
|
|
4477
|
-
* @example Routing user messages
|
|
4478
|
-
* User message flow:
|
|
4479
|
-
* channel.send('USER_LOGIN', {userId: 123})
|
|
4480
|
-
* -> NEW_MESSAGE action sent
|
|
4481
|
-
* -> Received by remote broker (this handler)
|
|
4482
|
-
* -> Routed to channel's onMessage handlers
|
|
4483
|
-
*/
|
|
4484
2305
|
function handleMessage(context, message) {
|
|
4485
2306
|
const { state, registry, logger } = context;
|
|
4486
2307
|
const action = message.data;
|
|
4487
|
-
const senderId = action.senderId;
|
|
4488
2308
|
if (!isActionWithData(action)) {
|
|
4489
2309
|
return;
|
|
4490
2310
|
}
|
|
4491
2311
|
const messageData = action.data;
|
|
4492
|
-
const channel =
|
|
2312
|
+
const channel = resolveChannel(registry, message);
|
|
4493
2313
|
if (!channel || !channel.isActive()) {
|
|
4494
2314
|
return;
|
|
4495
2315
|
}
|
|
@@ -4498,29 +2318,13 @@
|
|
|
4498
2318
|
logger.info(`${state.name} ignored message from ${channel.getName()}`);
|
|
4499
2319
|
return;
|
|
4500
2320
|
}
|
|
2321
|
+
if (!channel.getAcceptedTypes().includes(messageData.type)) {
|
|
2322
|
+
logger.info(`${state.name} dropped message type '${messageData.type}' not accepted by the ${channel.getName()} channel contract`);
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
4501
2325
|
channel.notifyMessage(messageData);
|
|
4502
2326
|
}
|
|
4503
2327
|
|
|
4504
|
-
/**
|
|
4505
|
-
* Handles OPEN_CONNECTION action.
|
|
4506
|
-
* Completes handshake on responder's side and notifies open event.
|
|
4507
|
-
*
|
|
4508
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
4509
|
-
* @param message - Message event containing the OPEN_CONNECTION action
|
|
4510
|
-
*
|
|
4511
|
-
* @remarks
|
|
4512
|
-
* Side Effects:
|
|
4513
|
-
* - Terminates the connection process
|
|
4514
|
-
* - Extracts security confirmation (if present)
|
|
4515
|
-
* - Marks security as ready if security is active
|
|
4516
|
-
* - Fires 'open' lifecycle event on responder's side
|
|
4517
|
-
* - Fires 'security-ready' event if security transport is active
|
|
4518
|
-
*
|
|
4519
|
-
* @example Completing the three-way handshake
|
|
4520
|
-
* Final step of three-way handshake:
|
|
4521
|
-
* Responder receives OPEN (this handler) from Initiator
|
|
4522
|
-
* Both sides now have active connection
|
|
4523
|
-
*/
|
|
4524
2328
|
function handleOpen(context, message) {
|
|
4525
2329
|
const { state, processManager, logger } = context;
|
|
4526
2330
|
const action = message.data;
|
|
@@ -4548,33 +2352,6 @@
|
|
|
4548
2352
|
channel.notifyEvent('open', { origin: message.origin });
|
|
4549
2353
|
}
|
|
4550
2354
|
|
|
4551
|
-
/**
|
|
4552
|
-
* Protocol negotiation logic for security handshake.
|
|
4553
|
-
*
|
|
4554
|
-
* Implements the negotiation algorithm that determines the best
|
|
4555
|
-
* security protocol to use between two communicating parties.
|
|
4556
|
-
*
|
|
4557
|
-
* @module security/negotiation/negotiate
|
|
4558
|
-
*/
|
|
4559
|
-
/**
|
|
4560
|
-
* Negotiates the best security protocol between initiator and responder.
|
|
4561
|
-
*
|
|
4562
|
-
* The algorithm iterates through the initiator's supported protocols
|
|
4563
|
-
* (in preference order) and selects the first one that the responder
|
|
4564
|
-
* also supports. If no overlap is found, falls back to 'none'.
|
|
4565
|
-
*
|
|
4566
|
-
* @param request - The initiator's security negotiation request
|
|
4567
|
-
* @param responderSupported - Protocols supported by the responder
|
|
4568
|
-
* @returns The negotiation result with the selected protocol
|
|
4569
|
-
*
|
|
4570
|
-
* @example Negotiating security protocol
|
|
4571
|
-
* ```typescript
|
|
4572
|
-
* const request = { supported: ['v2', 'v1', 'none'], preferred: 'v2' }
|
|
4573
|
-
* const responderSupported = ['v1', 'none']
|
|
4574
|
-
* const result = negotiateProtocol(request, responderSupported)
|
|
4575
|
-
* // result.negotiated === 'v1' (first match from initiator's list)
|
|
4576
|
-
* ```
|
|
4577
|
-
*/
|
|
4578
2355
|
function negotiateProtocol(request, responderSupported) {
|
|
4579
2356
|
for (const protocol of request.supported) {
|
|
4580
2357
|
if (responderSupported.includes(protocol)) {
|
|
@@ -4589,55 +2366,12 @@
|
|
|
4589
2366
|
isPreferred: request.preferred === 'none',
|
|
4590
2367
|
};
|
|
4591
2368
|
}
|
|
4592
|
-
/**
|
|
4593
|
-
* Creates a security negotiation response for the responder.
|
|
4594
|
-
*
|
|
4595
|
-
* Builds a response object containing the negotiated protocol
|
|
4596
|
-
* and optional public parameters for protocol initialization.
|
|
4597
|
-
*
|
|
4598
|
-
* @param negotiated - The negotiated protocol version
|
|
4599
|
-
* @param publicParams - Optional public parameters (e.g., key exchange hints)
|
|
4600
|
-
* @returns A security negotiation response object
|
|
4601
|
-
*
|
|
4602
|
-
* @example Creating responder response
|
|
4603
|
-
* ```typescript
|
|
4604
|
-
* const response = createSecurityResponse('v2', { hint: 'value' })
|
|
4605
|
-
* // { negotiated: 'v2', publicParams: { hint: 'value' } }
|
|
4606
|
-
* ```
|
|
4607
|
-
*/
|
|
4608
2369
|
function createSecurityResponse(negotiated, publicParams) {
|
|
4609
2370
|
const response = { negotiated };
|
|
4610
2371
|
return freeze(response);
|
|
4611
2372
|
}
|
|
4612
2373
|
|
|
4613
|
-
/**
|
|
4614
|
-
* Default supported security protocols for the responder.
|
|
4615
|
-
* Includes 'none' as fallback for backward compatibility.
|
|
4616
|
-
*/
|
|
4617
2374
|
const DEFAULT_RESPONDER_SUPPORTED = ['none'];
|
|
4618
|
-
/**
|
|
4619
|
-
* Handles REQUEST_CONNECTION action.
|
|
4620
|
-
* Creates or retrieves channel and initiates connection handshake.
|
|
4621
|
-
*
|
|
4622
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
4623
|
-
* @param message - Message event containing the REQUEST_CONNECTION action
|
|
4624
|
-
*
|
|
4625
|
-
* @remarks
|
|
4626
|
-
* Side Effects:
|
|
4627
|
-
* - Creates new channel if not found in registry
|
|
4628
|
-
* - Tracks process ID for handshake completion
|
|
4629
|
-
* - Negotiates security protocol if security data present
|
|
4630
|
-
* - Sends ACCEPT_CONNECTION if validation passes
|
|
4631
|
-
* - Sends DENY_CONNECTION if contract or security policy fails
|
|
4632
|
-
*
|
|
4633
|
-
* @example Processing connection requests
|
|
4634
|
-
* Incoming action triggers:
|
|
4635
|
-
* 1. Channel lookup/creation
|
|
4636
|
-
* 2. Contract validation
|
|
4637
|
-
* 3. Security policy check
|
|
4638
|
-
* 4. Security protocol negotiation (if applicable)
|
|
4639
|
-
* 5. ACCEPT_CONNECTION response (or DENY if validation fails)
|
|
4640
|
-
*/
|
|
4641
2375
|
function handleRequest(context, message) {
|
|
4642
2376
|
const { state, registry, processManager, actions, logger } = context;
|
|
4643
2377
|
const action = message.data;
|
|
@@ -4722,22 +2456,6 @@
|
|
|
4722
2456
|
});
|
|
4723
2457
|
}
|
|
4724
2458
|
|
|
4725
|
-
/**
|
|
4726
|
-
* Security error handling utilities.
|
|
4727
|
-
*
|
|
4728
|
-
* Provides functions for handling, categorizing, and emitting security-related
|
|
4729
|
-
* errors during message encryption/decryption operations.
|
|
4730
|
-
*
|
|
4731
|
-
* @module security/errors
|
|
4732
|
-
*/
|
|
4733
|
-
/**
|
|
4734
|
-
* Security error class with additional metadata for programmatic handling.
|
|
4735
|
-
*
|
|
4736
|
-
* @example Creating security error
|
|
4737
|
-
* ```typescript
|
|
4738
|
-
* throw new SecurityError('Decryption failed', 'decryption_failed', originalError)
|
|
4739
|
-
* ```
|
|
4740
|
-
*/
|
|
4741
2459
|
class SecurityError extends Error {
|
|
4742
2460
|
code;
|
|
4743
2461
|
originalCause;
|
|
@@ -4749,25 +2467,6 @@
|
|
|
4749
2467
|
setPrototypeOf(this, SecurityError.prototype);
|
|
4750
2468
|
}
|
|
4751
2469
|
}
|
|
4752
|
-
/**
|
|
4753
|
-
* Creates security error event data from an error.
|
|
4754
|
-
*
|
|
4755
|
-
* Converts various error types into a standardized SecurityErrorEventData
|
|
4756
|
-
* structure for emitting via channel events.
|
|
4757
|
-
*
|
|
4758
|
-
* @param error - The error to convert
|
|
4759
|
-
* @returns Standardized security error event data
|
|
4760
|
-
*
|
|
4761
|
-
* @example Converting errors to event data
|
|
4762
|
-
* ```typescript
|
|
4763
|
-
* try {
|
|
4764
|
-
* decrypt(payload)
|
|
4765
|
-
* } catch (error) {
|
|
4766
|
-
* const eventData = createSecurityErrorEventData(error)
|
|
4767
|
-
* channel.notifyEvent('security-error', eventData)
|
|
4768
|
-
* }
|
|
4769
|
-
* ```
|
|
4770
|
-
*/
|
|
4771
2470
|
function createSecurityErrorEventData(error) {
|
|
4772
2471
|
if (error instanceof SecurityError) {
|
|
4773
2472
|
return {
|
|
@@ -4789,17 +2488,6 @@
|
|
|
4789
2488
|
code: 'unknown',
|
|
4790
2489
|
};
|
|
4791
2490
|
}
|
|
4792
|
-
/**
|
|
4793
|
-
* Categorizes an error into a security error code.
|
|
4794
|
-
*
|
|
4795
|
-
* Analyzes the error message to determine the appropriate category.
|
|
4796
|
-
* This is used when errors from network-protocol are caught.
|
|
4797
|
-
*
|
|
4798
|
-
* @param error - The error to categorize
|
|
4799
|
-
* @returns The appropriate security error code
|
|
4800
|
-
*
|
|
4801
|
-
* @internal
|
|
4802
|
-
*/
|
|
4803
2491
|
function categorizeError(error) {
|
|
4804
2492
|
const message = error.message.toLowerCase();
|
|
4805
2493
|
if (message.includes('decrypt') || message.includes('invalid key') || message.includes('corrupted') || message.includes('cipher')) {
|
|
@@ -4816,21 +2504,6 @@
|
|
|
4816
2504
|
}
|
|
4817
2505
|
return 'unknown';
|
|
4818
2506
|
}
|
|
4819
|
-
/**
|
|
4820
|
-
* Logs a security error with appropriate formatting.
|
|
4821
|
-
*
|
|
4822
|
-
* Uses logger.error for actual errors and logger.warn for
|
|
4823
|
-
* retryable/expected failures.
|
|
4824
|
-
*
|
|
4825
|
-
* @param logger - Logger instance to use for output
|
|
4826
|
-
* @param channelName - Name of the channel where error occurred
|
|
4827
|
-
* @param error - The security error event data containing message, code, and optional cause
|
|
4828
|
-
*
|
|
4829
|
-
* @example Logging security errors
|
|
4830
|
-
* ```typescript
|
|
4831
|
-
* logSecurityError(logger, 'my-channel', errorData)
|
|
4832
|
-
* ```
|
|
4833
|
-
*/
|
|
4834
2507
|
function logSecurityError(logger, channelName, error) {
|
|
4835
2508
|
const prefix = `${channelName} security error:`;
|
|
4836
2509
|
if (error.code === 'unknown') {
|
|
@@ -4841,37 +2514,6 @@
|
|
|
4841
2514
|
}
|
|
4842
2515
|
}
|
|
4843
2516
|
|
|
4844
|
-
/**
|
|
4845
|
-
* Route encrypted messages through security transport.
|
|
4846
|
-
*
|
|
4847
|
-
* Handles Uint8Array payloads received via postMessage, routing them
|
|
4848
|
-
* through the appropriate channel's security transport for decryption.
|
|
4849
|
-
*
|
|
4850
|
-
* @module broker/routing/route-encrypted-message
|
|
4851
|
-
*/
|
|
4852
|
-
/**
|
|
4853
|
-
* Routes an encrypted message to the appropriate channel for decryption.
|
|
4854
|
-
*
|
|
4855
|
-
* This function handles Uint8Array payloads received via postMessage:
|
|
4856
|
-
* 1. Identifies the target channel based on message origin
|
|
4857
|
-
* 2. Routes the encrypted payload through the channel's security transport
|
|
4858
|
-
* 3. The security transport decrypts and invokes the registered receive handler
|
|
4859
|
-
*
|
|
4860
|
-
* If no matching channel is found or the channel has no security transport,
|
|
4861
|
-
* the message is silently dropped (with optional debug logging).
|
|
4862
|
-
*
|
|
4863
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
4864
|
-
* @param router - Message router for handling decrypted actions
|
|
4865
|
-
* @param event - Message event containing the encrypted Uint8Array payload
|
|
4866
|
-
*
|
|
4867
|
-
* @example Routing encrypted payloads
|
|
4868
|
-
* ```typescript
|
|
4869
|
-
* // In broker's onMessage handler:
|
|
4870
|
-
* if (event.data instanceof Uint8Array) {
|
|
4871
|
-
* routeEncryptedMessage(routingContext, router, event)
|
|
4872
|
-
* }
|
|
4873
|
-
* ```
|
|
4874
|
-
*/
|
|
4875
2517
|
function routeEncryptedMessage(context, router, event) {
|
|
4876
2518
|
const { state, registry, logger } = context;
|
|
4877
2519
|
const origin = event?.origin;
|
|
@@ -4908,19 +2550,6 @@
|
|
|
4908
2550
|
channel.notifyEvent('security-error', errorData);
|
|
4909
2551
|
}
|
|
4910
2552
|
}
|
|
4911
|
-
/**
|
|
4912
|
-
* Finds a channel by origin in the registry.
|
|
4913
|
-
*
|
|
4914
|
-
* Since encrypted messages don't contain senderId, we must match
|
|
4915
|
-
* by origin. This works because each channel has a unique target window
|
|
4916
|
-
* and thus a unique origin.
|
|
4917
|
-
*
|
|
4918
|
-
* @param registry - Channel registry to search
|
|
4919
|
-
* @param origin - Origin of the message sender
|
|
4920
|
-
* @returns The matching channel handle, or undefined if not found
|
|
4921
|
-
*
|
|
4922
|
-
* @internal
|
|
4923
|
-
*/
|
|
4924
2553
|
function findChannelByOrigin(registry, origin) {
|
|
4925
2554
|
const allChannels = getAll(registry);
|
|
4926
2555
|
for (const channel of allChannels) {
|
|
@@ -4939,30 +2568,10 @@
|
|
|
4939
2568
|
return undefined;
|
|
4940
2569
|
}
|
|
4941
2570
|
|
|
4942
|
-
/**
|
|
4943
|
-
* Logs an action in a structured format.
|
|
4944
|
-
*
|
|
4945
|
-
* @param logger - Logger instance
|
|
4946
|
-
* @param action - Action to log
|
|
4947
|
-
* @param direction - Direction of action ('sent' or 'received')
|
|
4948
|
-
*/
|
|
4949
2571
|
function logAction(logger, action, direction) {
|
|
4950
2572
|
logger.debug(`Action ${direction}:`, action.type, action);
|
|
4951
2573
|
}
|
|
4952
2574
|
|
|
4953
|
-
/**
|
|
4954
|
-
* Routes a message to the appropriate handler.
|
|
4955
|
-
*
|
|
4956
|
-
* @param router - Handler map containing action type to handler mappings
|
|
4957
|
-
* @param context - Routing context with state, registry, actions, and logger
|
|
4958
|
-
* @param message - Incoming message event containing the action to route
|
|
4959
|
-
*
|
|
4960
|
-
* @example Routing actions to handlers
|
|
4961
|
-
* ```typescript
|
|
4962
|
-
* const router = createRouter({ 'MESSAGE': handleMessage })
|
|
4963
|
-
* routeMessage(router, routingContext, incomingEvent)
|
|
4964
|
-
* ```
|
|
4965
|
-
*/
|
|
4966
2575
|
function routeMessage(router, context, message) {
|
|
4967
2576
|
const { logger } = context;
|
|
4968
2577
|
try {
|
|
@@ -4985,26 +2594,6 @@
|
|
|
4985
2594
|
}
|
|
4986
2595
|
}
|
|
4987
2596
|
|
|
4988
|
-
/**
|
|
4989
|
-
* Filters message origin against whitelist/blacklist
|
|
4990
|
-
*
|
|
4991
|
-
* @param origin - The origin to check
|
|
4992
|
-
* @param whitelist - List of allowed origins (takes precedence)
|
|
4993
|
-
* @param blacklist - List of blocked origins
|
|
4994
|
-
* @returns true if origin is allowed, false otherwise
|
|
4995
|
-
*
|
|
4996
|
-
* @example Filtering with a whitelist
|
|
4997
|
-
* ```typescript
|
|
4998
|
-
* filterOrigin('https://app.example.com', ['https://app.example.com'])
|
|
4999
|
-
* // => true
|
|
5000
|
-
* ```
|
|
5001
|
-
*
|
|
5002
|
-
* @example Filtering with a blacklist
|
|
5003
|
-
* ```typescript
|
|
5004
|
-
* filterOrigin('https://untrusted.com', [], ['https://untrusted.com'])
|
|
5005
|
-
* // => false
|
|
5006
|
-
* ```
|
|
5007
|
-
*/
|
|
5008
2597
|
function filterOrigin(origin, whitelist = [], blacklist = []) {
|
|
5009
2598
|
if (whitelist && whitelist.length > 0) {
|
|
5010
2599
|
return whitelist.includes(origin);
|
|
@@ -5015,53 +2604,21 @@
|
|
|
5015
2604
|
return true;
|
|
5016
2605
|
}
|
|
5017
2606
|
|
|
5018
|
-
/**
|
|
5019
|
-
* Validates that a security policy is a function
|
|
5020
|
-
*
|
|
5021
|
-
* @param policy - The policy to validate
|
|
5022
|
-
* @throws {Error} If policy is not a function
|
|
5023
|
-
*
|
|
5024
|
-
* @example Valid policy function
|
|
5025
|
-
* ```typescript
|
|
5026
|
-
* validatePolicy((event) => event.origin === 'https://trusted.com')
|
|
5027
|
-
* // No error thrown
|
|
5028
|
-
* ```
|
|
5029
|
-
*
|
|
5030
|
-
* @example Invalid policy throws
|
|
5031
|
-
* ```typescript
|
|
5032
|
-
* validatePolicy('not-a-function')
|
|
5033
|
-
* // Throws: Security policy must be a function...
|
|
5034
|
-
* ```
|
|
5035
|
-
*/
|
|
5036
2607
|
function validatePolicy(policy) {
|
|
5037
2608
|
if (typeof policy !== 'function') {
|
|
5038
2609
|
throw createError('Security policy must be a function that returns true or false.');
|
|
5039
2610
|
}
|
|
5040
2611
|
}
|
|
5041
2612
|
|
|
5042
|
-
/**
|
|
5043
|
-
* Creates a message broker instance
|
|
5044
|
-
*
|
|
5045
|
-
* @param config - Broker configuration
|
|
5046
|
-
* @param config.name - Unique name for the broker instance
|
|
5047
|
-
* @param config.contract - Channel contract defining message protocols
|
|
5048
|
-
* @param config.settings - Optional configuration overrides for broker behavior
|
|
5049
|
-
* @returns Broker handle with public API
|
|
5050
|
-
*
|
|
5051
|
-
* @example Creating a message broker
|
|
5052
|
-
* ```typescript
|
|
5053
|
-
* const broker = createBroker({
|
|
5054
|
-
* name: 'app-broker',
|
|
5055
|
-
* contract: { messages: { ping: {}, pong: {} } },
|
|
5056
|
-
* settings: { logLevel: 'warn' },
|
|
5057
|
-
* })
|
|
5058
|
-
* ```
|
|
5059
|
-
*/
|
|
5060
2613
|
function createBroker(config) {
|
|
5061
2614
|
assertNoCircularRef(config.contract, 'config.contract');
|
|
5062
2615
|
assertNoCircularRef(config.settings, 'config.settings');
|
|
5063
2616
|
validateName(config.name);
|
|
5064
2617
|
validateContract$1(config.contract);
|
|
2618
|
+
const brokerWindow = config.window ?? (typeof window !== 'undefined' ? window : undefined);
|
|
2619
|
+
if (!brokerWindow) {
|
|
2620
|
+
throw createError('Cannot create broker: no window is available. Pass an explicit `window` in the broker config when running outside a browser environment.');
|
|
2621
|
+
}
|
|
5065
2622
|
const mergedSettings = {
|
|
5066
2623
|
...defaultBrokerSettings,
|
|
5067
2624
|
...config.settings,
|
|
@@ -5075,7 +2632,7 @@
|
|
|
5075
2632
|
const state = {
|
|
5076
2633
|
id: uuidV4(),
|
|
5077
2634
|
name: config.name,
|
|
5078
|
-
window:
|
|
2635
|
+
window: brokerWindow,
|
|
5079
2636
|
contract: config.contract,
|
|
5080
2637
|
settings: mergedSettings,
|
|
5081
2638
|
logger,
|
|
@@ -5128,10 +2685,7 @@
|
|
|
5128
2685
|
}
|
|
5129
2686
|
routeMessage(router, routingContext, event);
|
|
5130
2687
|
};
|
|
5131
|
-
|
|
5132
|
-
if (typeof window !== 'undefined') {
|
|
5133
|
-
window.addEventListener('message', onMessage);
|
|
5134
|
-
}
|
|
2688
|
+
brokerWindow.addEventListener('message', onMessage);
|
|
5135
2689
|
const broker = {
|
|
5136
2690
|
id: state.id,
|
|
5137
2691
|
name: state.name,
|
|
@@ -5200,10 +2754,6 @@
|
|
|
5200
2754
|
return freeze(broker);
|
|
5201
2755
|
}
|
|
5202
2756
|
|
|
5203
|
-
/**
|
|
5204
|
-
* Default contract allowing any message type.
|
|
5205
|
-
* Useful for development and prototyping.
|
|
5206
|
-
*/
|
|
5207
2757
|
const DEFAULT_CONTRACT = freeze({
|
|
5208
2758
|
emitted: freeze([
|
|
5209
2759
|
{ type: 'MESSAGE', description: 'Generic message' },
|
|
@@ -5217,49 +2767,36 @@
|
|
|
5217
2767
|
{ type: 'ACK', description: 'Acknowledgment' },
|
|
5218
2768
|
]),
|
|
5219
2769
|
});
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
2770
|
+
|
|
2771
|
+
const _Reflect$1 = globalThis.Reflect;
|
|
2772
|
+
const get = _Reflect$1.get;
|
|
2773
|
+
const has = _Reflect$1.has;
|
|
2774
|
+
const ownKeys = _Reflect$1.ownKeys;
|
|
2775
|
+
const getOwnPropertyDescriptor = _Reflect$1.getOwnPropertyDescriptor;
|
|
2776
|
+
|
|
2777
|
+
let instance = null;
|
|
2778
|
+
function resolveBroker() {
|
|
2779
|
+
if (!instance) {
|
|
2780
|
+
instance = createBroker({
|
|
2781
|
+
name: 'default-broker',
|
|
2782
|
+
contract: DEFAULT_CONTRACT,
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2785
|
+
return instance;
|
|
2786
|
+
}
|
|
2787
|
+
new Proxy({}, {
|
|
2788
|
+
get: (_target, property) => get(resolveBroker(), property),
|
|
2789
|
+
has: (_target, property) => has(resolveBroker(), property),
|
|
2790
|
+
ownKeys: () => ownKeys(resolveBroker()),
|
|
2791
|
+
getOwnPropertyDescriptor: (_target, property) => {
|
|
2792
|
+
const descriptor = getOwnPropertyDescriptor(resolveBroker(), property);
|
|
2793
|
+
return descriptor ? { ...descriptor, configurable: true } : undefined;
|
|
2794
|
+
},
|
|
5241
2795
|
});
|
|
5242
2796
|
|
|
5243
|
-
// note: Runtime validation shared by the host/hostee factories and the config loader.
|
|
5244
|
-
/**
|
|
5245
|
-
* Narrows an unknown value to a non-null object.
|
|
5246
|
-
*
|
|
5247
|
-
* @param value - The value to test.
|
|
5248
|
-
* @returns `true` when the value is a non-null, non-array object.
|
|
5249
|
-
*/
|
|
5250
2797
|
function isRecord(value) {
|
|
5251
2798
|
return typeof value === 'object' && value !== null && !isArray(value);
|
|
5252
2799
|
}
|
|
5253
|
-
/**
|
|
5254
|
-
* Collects every problem with a single action list (`emitted` or `accepted`).
|
|
5255
|
-
*
|
|
5256
|
-
* Each malformed entry contributes its own message, distinguishing a non-object
|
|
5257
|
-
* entry from one missing a usable `type`, so the caller can report them all at once.
|
|
5258
|
-
*
|
|
5259
|
-
* @param actions - The candidate action list.
|
|
5260
|
-
* @param field - The field name, used to locate problems in messages.
|
|
5261
|
-
* @param issues - The running list of human-readable problems, appended to in place.
|
|
5262
|
-
*/
|
|
5263
2800
|
function collectActionListIssues(actions, field, issues) {
|
|
5264
2801
|
if (!isArray(actions)) {
|
|
5265
2802
|
issues.push(`"${field}" must be an array.`);
|
|
@@ -5273,14 +2810,21 @@
|
|
|
5273
2810
|
if (typeof action['type'] !== 'string' || action['type'].length === 0) {
|
|
5274
2811
|
issues.push(`"${field}[${index}]" must have a non-empty string "type".`);
|
|
5275
2812
|
}
|
|
2813
|
+
if (action['respondsWith'] !== undefined && (typeof action['respondsWith'] !== 'string' || action['respondsWith'].length === 0)) {
|
|
2814
|
+
issues.push(`"${field}[${index}]" has a "respondsWith" that must be a non-empty string.`);
|
|
2815
|
+
}
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
function collectRespondsWithIssues(actions, field, other, otherField, issues) {
|
|
2819
|
+
actions.forEach((action, index) => {
|
|
2820
|
+
if (action.respondsWith === undefined) {
|
|
2821
|
+
return;
|
|
2822
|
+
}
|
|
2823
|
+
if (!other.some((candidate) => candidate.type === action.respondsWith)) {
|
|
2824
|
+
issues.push(`"${field}[${index}]" responds with "${action.respondsWith}", but "${otherField}" has no action of that type.`);
|
|
2825
|
+
}
|
|
5276
2826
|
});
|
|
5277
2827
|
}
|
|
5278
|
-
/**
|
|
5279
|
-
* Names the kind of an unexpected value for an error message.
|
|
5280
|
-
*
|
|
5281
|
-
* @param value - The value to describe.
|
|
5282
|
-
* @returns A short label such as `null`, `an array`, or `a number`.
|
|
5283
|
-
*/
|
|
5284
2828
|
function describeType(value) {
|
|
5285
2829
|
if (value === null) {
|
|
5286
2830
|
return 'null';
|
|
@@ -5290,22 +2834,6 @@
|
|
|
5290
2834
|
}
|
|
5291
2835
|
return `a ${typeof value}`;
|
|
5292
2836
|
}
|
|
5293
|
-
/**
|
|
5294
|
-
* Validates an unknown value as a {@link FeatureContract}.
|
|
5295
|
-
*
|
|
5296
|
-
* Reports every malformed action at once rather than stopping at the first, so a
|
|
5297
|
-
* single error message lists all the problems to fix.
|
|
5298
|
-
*
|
|
5299
|
-
* @param contract - The candidate contract, typically parsed from disk.
|
|
5300
|
-
* @returns The validated contract, typed.
|
|
5301
|
-
* @throws {Error} When the value is not an object, or any action is malformed.
|
|
5302
|
-
*
|
|
5303
|
-
* @example Validating a parsed contract file
|
|
5304
|
-
* ```typescript
|
|
5305
|
-
* const contract = validateContract(parse(readFileSync('clock.contract.json', 'utf8')))
|
|
5306
|
-
* contract.emitted.forEach((action) => console.log(action.type))
|
|
5307
|
-
* ```
|
|
5308
|
-
*/
|
|
5309
2837
|
function validateContract(contract) {
|
|
5310
2838
|
if (!isRecord(contract)) {
|
|
5311
2839
|
throw createError(`Invalid contract: expected an object with "emitted" and "accepted" arrays, but got ${describeType(contract)}.`);
|
|
@@ -5313,6 +2841,12 @@
|
|
|
5313
2841
|
const issues = [];
|
|
5314
2842
|
collectActionListIssues(contract['emitted'], 'emitted', issues);
|
|
5315
2843
|
collectActionListIssues(contract['accepted'], 'accepted', issues);
|
|
2844
|
+
if (issues.length === 0) {
|
|
2845
|
+
const emitted = contract['emitted'];
|
|
2846
|
+
const accepted = contract['accepted'];
|
|
2847
|
+
collectRespondsWithIssues(emitted, 'emitted', accepted, 'accepted', issues);
|
|
2848
|
+
collectRespondsWithIssues(accepted, 'accepted', emitted, 'emitted', issues);
|
|
2849
|
+
}
|
|
5316
2850
|
if (issues.length > 0) {
|
|
5317
2851
|
throw createError(`Invalid contract:\n${issues.map((issue) => ` - ${issue}`).join('\n')}`);
|
|
5318
2852
|
}
|
|
@@ -5322,47 +2856,29 @@
|
|
|
5322
2856
|
};
|
|
5323
2857
|
}
|
|
5324
2858
|
|
|
5325
|
-
|
|
5326
|
-
* Internal control message types carried on the feature channel and hidden from consumers.
|
|
5327
|
-
*/
|
|
2859
|
+
const CONTROL_PREFIX = '__hf:';
|
|
5328
2860
|
const ControlType = freeze({
|
|
5329
|
-
/** Hostee-to-host liveness beat. */
|
|
5330
2861
|
Beat: '__hf:beat',
|
|
5331
|
-
/** Hostee-to-host content-size announcement. */
|
|
5332
2862
|
Size: '__hf:size',
|
|
2863
|
+
Request: '__hf:request',
|
|
2864
|
+
Response: '__hf:response',
|
|
5333
2865
|
});
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
* @param contract - The consumer-facing feature contract.
|
|
5338
|
-
* @returns A contract that additionally permits the reserved control types.
|
|
5339
|
-
*
|
|
5340
|
-
* @example Building a broker contract that carries the control plane
|
|
5341
|
-
* ```typescript
|
|
5342
|
-
* const broker = createBroker({ name, contract: withControlContract(contract) })
|
|
5343
|
-
* ```
|
|
5344
|
-
*/
|
|
2866
|
+
function isControlType(type) {
|
|
2867
|
+
return type.startsWith(CONTROL_PREFIX);
|
|
2868
|
+
}
|
|
5345
2869
|
function withControlContract(contract) {
|
|
5346
|
-
|
|
2870
|
+
const controlActions = () => [
|
|
2871
|
+
{ type: ControlType.Beat },
|
|
2872
|
+
{ type: ControlType.Size },
|
|
2873
|
+
{ type: ControlType.Request },
|
|
2874
|
+
{ type: ControlType.Response },
|
|
2875
|
+
];
|
|
5347
2876
|
return {
|
|
5348
|
-
emitted: [...contract.emitted,
|
|
5349
|
-
accepted: [...contract.accepted,
|
|
2877
|
+
emitted: [...contract.emitted, ...controlActions()],
|
|
2878
|
+
accepted: [...contract.accepted, ...controlActions()],
|
|
5350
2879
|
};
|
|
5351
2880
|
}
|
|
5352
2881
|
|
|
5353
|
-
/**
|
|
5354
|
-
* Creates an {@link EventEmitter} backed by a plain registry.
|
|
5355
|
-
*
|
|
5356
|
-
* @returns A frozen emitter instance.
|
|
5357
|
-
*
|
|
5358
|
-
* @example Subscribing and emitting
|
|
5359
|
-
* ```typescript
|
|
5360
|
-
* const emitter = createEventEmitter()
|
|
5361
|
-
* const off = emitter.on('open', () => console.log('opened'))
|
|
5362
|
-
* emitter.emit('open')
|
|
5363
|
-
* off()
|
|
5364
|
-
* ```
|
|
5365
|
-
*/
|
|
5366
2882
|
function createEventEmitter() {
|
|
5367
2883
|
const registry = {};
|
|
5368
2884
|
const on = (event, handler) => {
|
|
@@ -5378,111 +2894,111 @@
|
|
|
5378
2894
|
return freeze({ on, emit });
|
|
5379
2895
|
}
|
|
5380
2896
|
|
|
5381
|
-
/**
|
|
5382
|
-
* Safe Promise factory and bound static methods.
|
|
5383
|
-
*
|
|
5384
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/promise
|
|
5385
|
-
*/
|
|
5386
|
-
/* eslint-disable workspace/lib-require-jsdoc-example */
|
|
5387
2897
|
const _Promise = globalThis.Promise;
|
|
5388
2898
|
const _Reflect = globalThis.Reflect;
|
|
5389
|
-
/**
|
|
5390
|
-
* (Safe copy) Creates a new Promise using the captured Promise constructor.
|
|
5391
|
-
* Use this instead of `new Promise()`.
|
|
5392
|
-
*
|
|
5393
|
-
* @param executor - The executor function.
|
|
5394
|
-
* @returns A new Promise instance.
|
|
5395
|
-
*/
|
|
5396
2899
|
const createPromise = (executor) => _Reflect.construct(_Promise, [executor]);
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
*/
|
|
5400
|
-
_Promise.resolve.bind(_Promise);
|
|
5401
|
-
/**
|
|
5402
|
-
* (Safe copy) Returns a Promise that rejects with the given reason.
|
|
5403
|
-
*/
|
|
5404
|
-
_Promise.reject.bind(_Promise);
|
|
5405
|
-
/**
|
|
5406
|
-
* (Safe copy) Returns a Promise that resolves when all promises resolve.
|
|
5407
|
-
*/
|
|
2900
|
+
const promiseResolve = _Promise.resolve.bind(_Promise);
|
|
2901
|
+
const promiseReject = _Promise.reject.bind(_Promise);
|
|
5408
2902
|
_Promise.all.bind(_Promise);
|
|
5409
|
-
/**
|
|
5410
|
-
* (Safe copy) Returns a Promise that resolves/rejects with the first settled promise.
|
|
5411
|
-
*/
|
|
5412
2903
|
_Promise.race.bind(_Promise);
|
|
5413
|
-
/**
|
|
5414
|
-
* (Safe copy) Returns a Promise that resolves when all promises settle.
|
|
5415
|
-
*/
|
|
5416
2904
|
_Promise.allSettled.bind(_Promise);
|
|
5417
|
-
/**
|
|
5418
|
-
* (Safe copy) Returns a Promise that resolves with the first fulfilled promise.
|
|
5419
|
-
*/
|
|
5420
2905
|
_Promise.any.bind(_Promise);
|
|
5421
|
-
/**
|
|
5422
|
-
* (Safe copy) Creates a Promise along with its resolve and reject functions.
|
|
5423
|
-
* Note: Available only in ES2024+ environments.
|
|
5424
|
-
*/
|
|
5425
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5426
2906
|
_Promise.withResolvers?.bind(_Promise);
|
|
5427
2907
|
|
|
5428
|
-
|
|
5429
|
-
* Safe copies of Timer/Scheduling built-in functions.
|
|
5430
|
-
*
|
|
5431
|
-
* These references are captured at module initialization time to protect against
|
|
5432
|
-
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
5433
|
-
*
|
|
5434
|
-
* @module @hyperfrontend/immutable-api-utils/built-in-copy/timers
|
|
5435
|
-
*/
|
|
2908
|
+
const _setTimeout = globalThis.setTimeout;
|
|
5436
2909
|
const _setInterval = globalThis.setInterval;
|
|
2910
|
+
const _clearTimeout = globalThis.clearTimeout;
|
|
5437
2911
|
const _clearInterval = globalThis.clearInterval;
|
|
5438
|
-
|
|
5439
|
-
* (Safe copy) Repeatedly calls a function with a fixed time delay between each call.
|
|
5440
|
-
*
|
|
5441
|
-
* @param callback - Function to call at each interval.
|
|
5442
|
-
* @param delay - Time in milliseconds between calls.
|
|
5443
|
-
* @param args - Additional arguments to pass to the callback.
|
|
5444
|
-
* @returns A numeric ID for the interval.
|
|
5445
|
-
*
|
|
5446
|
-
* @example Setting an interval
|
|
5447
|
-
* ```typescript
|
|
5448
|
-
* let count = 0
|
|
5449
|
-
* const intervalId = setInterval(() => {
|
|
5450
|
-
* count++
|
|
5451
|
-
* if (count >= 5) clearInterval(intervalId)
|
|
5452
|
-
* }, 1000)
|
|
5453
|
-
* ```
|
|
5454
|
-
*/
|
|
2912
|
+
const setTimeout = (callback, delay, ...args) => _setTimeout(callback, delay, ...args);
|
|
5455
2913
|
const setInterval = (callback, delay, ...args) => _setInterval(callback, delay, ...args);
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
* @param id - The identifier of the interval to cancel.
|
|
5460
|
-
*
|
|
5461
|
-
* @example Canceling an interval
|
|
5462
|
-
* ```typescript
|
|
5463
|
-
* const intervalId = setInterval(() => console.log('tick'), 1000)
|
|
5464
|
-
* // Stop after some condition
|
|
5465
|
-
* clearInterval(intervalId)
|
|
5466
|
-
* ```
|
|
5467
|
-
*/
|
|
2914
|
+
const clearTimeout = (id) => {
|
|
2915
|
+
_clearTimeout(id);
|
|
2916
|
+
};
|
|
5468
2917
|
const clearInterval = (id) => {
|
|
5469
2918
|
_clearInterval(id);
|
|
5470
2919
|
};
|
|
5471
2920
|
|
|
5472
|
-
|
|
2921
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
2922
|
+
function createRequestPeer(origin, send) {
|
|
2923
|
+
let pending = {};
|
|
2924
|
+
const handlers = {};
|
|
2925
|
+
let nextCorrelation = 0;
|
|
2926
|
+
const take = (correlationId) => {
|
|
2927
|
+
const entry = pending[correlationId];
|
|
2928
|
+
if (entry) {
|
|
2929
|
+
clearTimeout(entry.timer);
|
|
2930
|
+
delete pending[correlationId];
|
|
2931
|
+
}
|
|
2932
|
+
return entry;
|
|
2933
|
+
};
|
|
2934
|
+
const respond = (request, outcome) => send(ControlType.Response, { correlationId: request.correlationId, from: origin, innerType: request.innerType, ...outcome });
|
|
2935
|
+
const answer = (request) => {
|
|
2936
|
+
const handler = handlers[request.innerType];
|
|
2937
|
+
if (!handler) {
|
|
2938
|
+
respond(request, { ok: false, error: `No handler is registered for '${request.innerType}'.` });
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
promiseResolve()
|
|
2942
|
+
.then(() => handler(request.payload))
|
|
2943
|
+
.then((payload) => respond(request, { ok: true, payload }), (error) => respond(request, { ok: false, error: error instanceof Error ? error.message : `${error}` }));
|
|
2944
|
+
};
|
|
2945
|
+
const settle = (response) => {
|
|
2946
|
+
const entry = take(response.correlationId);
|
|
2947
|
+
if (!entry) {
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
if (response.ok) {
|
|
2951
|
+
entry.resolve(response.payload);
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
entry.reject(createError(response.error ?? `Request '${response.innerType}' failed.`));
|
|
2955
|
+
};
|
|
2956
|
+
const dispatch = (type, data) => {
|
|
2957
|
+
const envelope = (typeof data === 'object' ? data : null);
|
|
2958
|
+
if (!envelope || envelope.from === origin) {
|
|
2959
|
+
return;
|
|
2960
|
+
}
|
|
2961
|
+
if (type === ControlType.Request) {
|
|
2962
|
+
answer(envelope);
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
if (type === ControlType.Response) {
|
|
2966
|
+
settle(envelope);
|
|
2967
|
+
}
|
|
2968
|
+
};
|
|
2969
|
+
const request = (type, data, options) => createPromise((resolve, reject) => {
|
|
2970
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2971
|
+
const correlationId = `${origin}-${(nextCorrelation += 1)}`;
|
|
2972
|
+
const timer = setTimeout(() => {
|
|
2973
|
+
delete pending[correlationId];
|
|
2974
|
+
reject(createError(`Request '${type}' timed out after ${timeoutMs}ms.`));
|
|
2975
|
+
}, timeoutMs);
|
|
2976
|
+
pending[correlationId] = { resolve, reject, timer };
|
|
2977
|
+
send(ControlType.Request, { correlationId, from: origin, innerType: type, payload: data });
|
|
2978
|
+
});
|
|
2979
|
+
const handle = (type, handler) => {
|
|
2980
|
+
if (handlers[type]) {
|
|
2981
|
+
throw createError(`A handler for '${type}' is already registered.`);
|
|
2982
|
+
}
|
|
2983
|
+
handlers[type] = handler;
|
|
2984
|
+
return () => {
|
|
2985
|
+
if (handlers[type] === handler) {
|
|
2986
|
+
delete handlers[type];
|
|
2987
|
+
}
|
|
2988
|
+
};
|
|
2989
|
+
};
|
|
2990
|
+
const rejectAll = (reason) => {
|
|
2991
|
+
const failed = values(pending);
|
|
2992
|
+
pending = {};
|
|
2993
|
+
for (const entry of failed) {
|
|
2994
|
+
clearTimeout(entry.timer);
|
|
2995
|
+
entry.reject(createError(reason));
|
|
2996
|
+
}
|
|
2997
|
+
};
|
|
2998
|
+
return freeze({ request, handle, dispatch, rejectAll });
|
|
2999
|
+
}
|
|
3000
|
+
|
|
5473
3001
|
const BEAT_INTERVAL_MS = 1000;
|
|
5474
|
-
/**
|
|
5475
|
-
* Creates the hostee-side heartbeat emitter.
|
|
5476
|
-
*
|
|
5477
|
-
* @param send - The control-channel send function (receives the reserved beat type).
|
|
5478
|
-
* @returns A start/stop handle for the beat loop.
|
|
5479
|
-
*
|
|
5480
|
-
* @example Pulsing the host while connected
|
|
5481
|
-
* ```typescript
|
|
5482
|
-
* const heartbeat = createHeartbeatEmitter((type) => channel.send(type))
|
|
5483
|
-
* heartbeat.start()
|
|
5484
|
-
* ```
|
|
5485
|
-
*/
|
|
5486
3002
|
function createHeartbeatEmitter(send) {
|
|
5487
3003
|
let timer;
|
|
5488
3004
|
return {
|
|
@@ -5499,24 +3015,6 @@
|
|
|
5499
3015
|
};
|
|
5500
3016
|
}
|
|
5501
3017
|
|
|
5502
|
-
/**
|
|
5503
|
-
* Observes an element for size changes and triggers a callback when resized.
|
|
5504
|
-
*
|
|
5505
|
-
* @param element - The element to observe for resize events
|
|
5506
|
-
* @param callback - The function to call when the element is resized
|
|
5507
|
-
* @returns A cleanup function to stop observing the element
|
|
5508
|
-
*
|
|
5509
|
-
* @example Observing element resize
|
|
5510
|
-
* ```typescript
|
|
5511
|
-
* const container = document.getElementById('resizable-panel')
|
|
5512
|
-
* const stopObserving = onElementResize(container, (rect) => {
|
|
5513
|
-
* console.log(`New size: ${rect.width}x${rect.height}`)
|
|
5514
|
-
* })
|
|
5515
|
-
*
|
|
5516
|
-
* // Stop observing when done
|
|
5517
|
-
* stopObserving()
|
|
5518
|
-
* ```
|
|
5519
|
-
*/
|
|
5520
3018
|
function onElementResize(element, callback) {
|
|
5521
3019
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
5522
3020
|
for (const entry of entries) {
|
|
@@ -5533,25 +3031,6 @@
|
|
|
5533
3031
|
};
|
|
5534
3032
|
}
|
|
5535
3033
|
|
|
5536
|
-
/**
|
|
5537
|
-
* Converts a CSS object into a CSS string suitable for inline styles or style sheets.
|
|
5538
|
-
* Automatically converts camelCase properties to kebab-case.
|
|
5539
|
-
*
|
|
5540
|
-
* @param cssObj - The CSS object with property-value pairs
|
|
5541
|
-
* @returns A CSS string representation
|
|
5542
|
-
*
|
|
5543
|
-
* @example Converting style object to CSS
|
|
5544
|
-
* ```typescript
|
|
5545
|
-
* const styles = {
|
|
5546
|
-
* backgroundColor: '#f0f0f0',
|
|
5547
|
-
* fontSize: '14px',
|
|
5548
|
-
* marginTop: '8px'
|
|
5549
|
-
* }
|
|
5550
|
-
*
|
|
5551
|
-
* cssObjectToString(styles)
|
|
5552
|
-
* // => 'background-color: #f0f0f0; font-size: 14px; margin-top: 8px; '
|
|
5553
|
-
* ```
|
|
5554
|
-
*/
|
|
5555
3034
|
function cssObjectToString(cssObj) {
|
|
5556
3035
|
const errors = [];
|
|
5557
3036
|
const cssString = entries(cssObj).reduce((prev, [property, value]) => {
|
|
@@ -5571,24 +3050,6 @@
|
|
|
5571
3050
|
return cssString;
|
|
5572
3051
|
}
|
|
5573
3052
|
|
|
5574
|
-
/**
|
|
5575
|
-
* Validates whether a string is a valid CSS selector by attempting to query with it.
|
|
5576
|
-
*
|
|
5577
|
-
* @param selector - The CSS selector string to validate
|
|
5578
|
-
* @returns True if the selector is valid, false otherwise
|
|
5579
|
-
*
|
|
5580
|
-
* @example Validating CSS selectors
|
|
5581
|
-
* ```typescript
|
|
5582
|
-
* isValidCssSelector('.my-class')
|
|
5583
|
-
* // => true
|
|
5584
|
-
*
|
|
5585
|
-
* isValidCssSelector('#header nav > ul')
|
|
5586
|
-
* // => true
|
|
5587
|
-
*
|
|
5588
|
-
* isValidCssSelector('[invalid')
|
|
5589
|
-
* // => false
|
|
5590
|
-
* ```
|
|
5591
|
-
*/
|
|
5592
3053
|
function isValidCssSelector(selector) {
|
|
5593
3054
|
try {
|
|
5594
3055
|
document.createDocumentFragment().querySelector(selector);
|
|
@@ -5599,31 +3060,6 @@
|
|
|
5599
3060
|
}
|
|
5600
3061
|
}
|
|
5601
3062
|
|
|
5602
|
-
/**
|
|
5603
|
-
* Generates a CSS rule string from a given selector and style declaration.
|
|
5604
|
-
*
|
|
5605
|
-
* This function takes a CSS selector and either a string or a CSSStyleDeclaration object
|
|
5606
|
-
* representing the styles to be applied. It validates the selector and converts the CSS
|
|
5607
|
-
* object to a string if needed. The function then constructs and returns a valid
|
|
5608
|
-
* CSS rule as a string.
|
|
5609
|
-
*
|
|
5610
|
-
* @param selector - The CSS selector to which the styles will be applied
|
|
5611
|
-
* @param css - The styles to apply, either as a string or CSSStyleDeclaration object
|
|
5612
|
-
* @returns A string representing a complete CSS rule
|
|
5613
|
-
* @throws {Error} When the selector is invalid or the css argument is not a valid string or object
|
|
5614
|
-
*
|
|
5615
|
-
* @example With style object
|
|
5616
|
-
* ```typescript
|
|
5617
|
-
* cssRule('.button', { padding: '8px 16px', borderRadius: '4px' })
|
|
5618
|
-
* // => '.button{padding: 8px 16px; border-radius: 4px}'
|
|
5619
|
-
* ```
|
|
5620
|
-
*
|
|
5621
|
-
* @example With CSS string
|
|
5622
|
-
* ```typescript
|
|
5623
|
-
* cssRule('.button:hover', 'background-color: #007bff; color: white')
|
|
5624
|
-
* // => '.button:hover{background-color: #007bff; color: white}'
|
|
5625
|
-
* ```
|
|
5626
|
-
*/
|
|
5627
3063
|
function cssRule(selector, css) {
|
|
5628
3064
|
if (!isValidCssSelector(selector)) {
|
|
5629
3065
|
throw createError('A valid css select must be provided');
|
|
@@ -5637,23 +3073,6 @@
|
|
|
5637
3073
|
return `${selector}{${css}}`;
|
|
5638
3074
|
}
|
|
5639
3075
|
|
|
5640
|
-
/**
|
|
5641
|
-
* Creates CSS rules from a styles object, converting each selector-style pair into CSS rule strings.
|
|
5642
|
-
*
|
|
5643
|
-
* @param styles - An object mapping CSS selectors to style objects
|
|
5644
|
-
* @returns A string containing all CSS rules separated by newlines
|
|
5645
|
-
*
|
|
5646
|
-
* @example Creating CSS rules from object
|
|
5647
|
-
* ```typescript
|
|
5648
|
-
* const styles = {
|
|
5649
|
-
* '.card': { padding: '16px', boxShadow: '0 2px 4px rgba(0,0,0,0.1)' },
|
|
5650
|
-
* '.card-title': { fontSize: '18px', fontWeight: 'bold' }
|
|
5651
|
-
* }
|
|
5652
|
-
*
|
|
5653
|
-
* cssRules(styles)
|
|
5654
|
-
* // => '.card{padding: 16px; box-shadow: 0 2px 4px rgba(0,0,0,0.1)}\n.card-title{font-size: 18px; font-weight: bold}'
|
|
5655
|
-
* ```
|
|
5656
|
-
*/
|
|
5657
3076
|
function cssRules(styles) {
|
|
5658
3077
|
if (getType(styles) !== 'object')
|
|
5659
3078
|
return '';
|
|
@@ -5666,33 +3085,6 @@
|
|
|
5666
3085
|
const labels = createSet();
|
|
5667
3086
|
const labeledStylesheets = createMap();
|
|
5668
3087
|
const stylesheetLabels = createWeakMap();
|
|
5669
|
-
/**
|
|
5670
|
-
* Adds a new stylesheet to the document with optional label.
|
|
5671
|
-
*
|
|
5672
|
-
* @param css - The CSS rules to be added in the new stylesheet
|
|
5673
|
-
* @param label - Optional label for the new stylesheet
|
|
5674
|
-
* @returns A tuple where the first item is the created HTMLStyleElement, and the second item is a cleanup function
|
|
5675
|
-
* @throws {Error} When css is not a string or StyleMap, is empty, or a stylesheet with the same label already exists
|
|
5676
|
-
*
|
|
5677
|
-
* @example CSS string
|
|
5678
|
-
* ```typescript
|
|
5679
|
-
* const [styleElement, removeStyles] = addStylesheet(`
|
|
5680
|
-
* .modal { display: flex; align-items: center; }
|
|
5681
|
-
* .modal-overlay { background: rgba(0, 0, 0, 0.5); }
|
|
5682
|
-
* `, 'modal-styles')
|
|
5683
|
-
*
|
|
5684
|
-
* // Remove when done
|
|
5685
|
-
* removeStyles()
|
|
5686
|
-
* ```
|
|
5687
|
-
*
|
|
5688
|
-
* @example StyleMap object
|
|
5689
|
-
* ```typescript
|
|
5690
|
-
* const [styleElement, removeStyles] = addStylesheet({
|
|
5691
|
-
* '.button': { backgroundColor: '#3498db', padding: '10px 20px' },
|
|
5692
|
-
* '.button:hover': { backgroundColor: '#2980b9' },
|
|
5693
|
-
* })
|
|
5694
|
-
* ```
|
|
5695
|
-
*/
|
|
5696
3088
|
function addStylesheet(css, label) {
|
|
5697
3089
|
if (getType(css) === 'object') {
|
|
5698
3090
|
css = cssRules(css);
|
|
@@ -5707,23 +3099,6 @@
|
|
|
5707
3099
|
const removeCallback = () => removeStylesheet(style);
|
|
5708
3100
|
return [style, removeCallback];
|
|
5709
3101
|
}
|
|
5710
|
-
/**
|
|
5711
|
-
* Removes a stylesheet from the document.
|
|
5712
|
-
*
|
|
5713
|
-
* @param {string | HTMLStyleElement} ref - The label or the HTMLStyleElement of the stylesheet to be removed.
|
|
5714
|
-
*
|
|
5715
|
-
* @example By label
|
|
5716
|
-
* ```typescript
|
|
5717
|
-
* addStylesheet('.theme { color: red; }', 'theme-styles')
|
|
5718
|
-
* removeStylesheet('theme-styles')
|
|
5719
|
-
* ```
|
|
5720
|
-
*
|
|
5721
|
-
* @example By element reference
|
|
5722
|
-
* ```typescript
|
|
5723
|
-
* const [styleElement] = addStylesheet('.custom { margin: 0; }')
|
|
5724
|
-
* removeStylesheet(styleElement)
|
|
5725
|
-
* ```
|
|
5726
|
-
*/
|
|
5727
3102
|
function removeStylesheet(ref) {
|
|
5728
3103
|
const isLabel = getType(ref) === 'string';
|
|
5729
3104
|
let style;
|
|
@@ -5741,7 +3116,6 @@
|
|
|
5741
3116
|
stylesheets.delete(style);
|
|
5742
3117
|
}
|
|
5743
3118
|
catch {
|
|
5744
|
-
/** Swallow any errors */
|
|
5745
3119
|
}
|
|
5746
3120
|
try {
|
|
5747
3121
|
labels.delete(label);
|
|
@@ -5749,35 +3123,13 @@
|
|
|
5749
3123
|
stylesheetLabels.delete(style);
|
|
5750
3124
|
}
|
|
5751
3125
|
catch {
|
|
5752
|
-
/** Swallow any errors */
|
|
5753
3126
|
}
|
|
5754
3127
|
}
|
|
5755
3128
|
|
|
5756
|
-
// note: The feature page resets its own body so the embedded UI fills the iframe edge-to-edge with a transparent backdrop; the host cannot touch the cross-origin body itself.
|
|
5757
3129
|
const BODY_RESET_CSS = 'html,body{margin:0;padding:0;background:transparent}';
|
|
5758
|
-
/**
|
|
5759
|
-
* Neutralizes the feature page's body so the embedded UI is neither clipped nor padded.
|
|
5760
|
-
*
|
|
5761
|
-
* @example Resetting the body when a feature initializes
|
|
5762
|
-
* ```typescript
|
|
5763
|
-
* applyBodyReset()
|
|
5764
|
-
* ```
|
|
5765
|
-
*/
|
|
5766
3130
|
function applyBodyReset() {
|
|
5767
3131
|
addStylesheet(BODY_RESET_CSS);
|
|
5768
3132
|
}
|
|
5769
|
-
/**
|
|
5770
|
-
* Creates the hostee-side content-size announcer.
|
|
5771
|
-
*
|
|
5772
|
-
* @param send - The control-channel send function (receives the reserved size type).
|
|
5773
|
-
* @returns A start/stop handle for the size-announcement loop.
|
|
5774
|
-
*
|
|
5775
|
-
* @example Announcing size to the host while connected
|
|
5776
|
-
* ```typescript
|
|
5777
|
-
* const announcer = createSizeAnnouncer((type, data) => channel.send(type, data))
|
|
5778
|
-
* announcer.start()
|
|
5779
|
-
* ```
|
|
5780
|
-
*/
|
|
5781
3133
|
function createSizeAnnouncer(send) {
|
|
5782
3134
|
let cleanup;
|
|
5783
3135
|
const announce = () => send(ControlType.Size, { width: document.body.scrollWidth, height: document.body.scrollHeight });
|
|
@@ -5798,18 +3150,6 @@
|
|
|
5798
3150
|
};
|
|
5799
3151
|
}
|
|
5800
3152
|
|
|
5801
|
-
// note: The host connects from a parent window (embedded iframe) or an opener window (popup/standalone); a top-level document has neither.
|
|
5802
|
-
/**
|
|
5803
|
-
* Resolves the window the host is expected to message the feature from.
|
|
5804
|
-
*
|
|
5805
|
-
* @param win - The feature's own window (`globalThis.window` in production).
|
|
5806
|
-
* @returns The parent or opener window, or `null` when running top-level.
|
|
5807
|
-
*
|
|
5808
|
-
* @example Resolving the host window
|
|
5809
|
-
* ```typescript
|
|
5810
|
-
* const hostWindow = resolveHostWindow(window)
|
|
5811
|
-
* ```
|
|
5812
|
-
*/
|
|
5813
3153
|
function resolveHostWindow(win) {
|
|
5814
3154
|
if (win.parent !== win) {
|
|
5815
3155
|
return win.parent;
|
|
@@ -5819,23 +3159,10 @@
|
|
|
5819
3159
|
}
|
|
5820
3160
|
return null;
|
|
5821
3161
|
}
|
|
5822
|
-
/**
|
|
5823
|
-
* Wires a hostee channel into the emitter and assembles the public handle.
|
|
5824
|
-
*
|
|
5825
|
-
* @param broker - The nexus broker for this feature.
|
|
5826
|
-
* @param hostWindow - The resolved host window, or `null` when unembedded.
|
|
5827
|
-
* @param emitter - The subscription registry backing `handle.on`.
|
|
5828
|
-
* @returns The frozen {@link FeatureHandle}.
|
|
5829
|
-
*
|
|
5830
|
-
* @example Assembling a feature handle around a broker
|
|
5831
|
-
* ```typescript
|
|
5832
|
-
* const handle = createFeatureHandle(broker, resolveHostWindow(window), emitter)
|
|
5833
|
-
* await handle.ready()
|
|
5834
|
-
* ```
|
|
5835
|
-
*/
|
|
5836
3162
|
function createFeatureHandle(broker, hostWindow, emitter) {
|
|
5837
3163
|
let channel = null;
|
|
5838
3164
|
let opened = false;
|
|
3165
|
+
const requests = createRequestPeer('feature', (type, data) => channel?.send(type, data));
|
|
5839
3166
|
if (hostWindow) {
|
|
5840
3167
|
const activeChannel = broker.addChannel('host', hostWindow);
|
|
5841
3168
|
channel = activeChannel;
|
|
@@ -5852,14 +3179,25 @@
|
|
|
5852
3179
|
emitter.emit('close');
|
|
5853
3180
|
heartbeat.stop();
|
|
5854
3181
|
announcer.stop();
|
|
3182
|
+
requests.rejectAll('The host channel closed before the host responded.');
|
|
5855
3183
|
});
|
|
5856
3184
|
activeChannel.on('deny', (data) => emitter.emit('error', data));
|
|
5857
3185
|
activeChannel.on('invalid', (data) => emitter.emit('error', data));
|
|
5858
|
-
activeChannel.onMessage((message) =>
|
|
3186
|
+
activeChannel.onMessage((message) => {
|
|
3187
|
+
if (isControlType(message.type)) {
|
|
3188
|
+
requests.dispatch(message.type, message.data);
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
emitter.emit(message.type, message.data);
|
|
3192
|
+
});
|
|
5859
3193
|
activeChannel.connect();
|
|
5860
3194
|
}
|
|
5861
3195
|
return freeze({
|
|
5862
3196
|
send: (type, data) => channel?.send(type, data),
|
|
3197
|
+
request: (type, data, options) => channel
|
|
3198
|
+
? requests.request(type, data, options)
|
|
3199
|
+
: promiseReject(createError(`Cannot send request '${type}': the feature is not connected to a host.`)),
|
|
3200
|
+
handle: requests.handle,
|
|
5863
3201
|
on: emitter.on,
|
|
5864
3202
|
ready: () => createPromise((resolve) => {
|
|
5865
3203
|
if (opened) {
|
|
@@ -5872,22 +3210,6 @@
|
|
|
5872
3210
|
});
|
|
5873
3211
|
}
|
|
5874
3212
|
|
|
5875
|
-
/**
|
|
5876
|
-
* Initializes a feature app on the hostee side and waits for the host connection.
|
|
5877
|
-
*
|
|
5878
|
-
* Creates a nexus broker for the feature, resolves the host window, and returns
|
|
5879
|
-
* a handle for messaging and lifecycle.
|
|
5880
|
-
*
|
|
5881
|
-
* @param options - Feature name and contract.
|
|
5882
|
-
* @returns A handle exposing `send`, `on`, `ready`, and `close`.
|
|
5883
|
-
*
|
|
5884
|
-
* @example Initializing a clock feature
|
|
5885
|
-
* ```typescript
|
|
5886
|
-
* const feature = createFeature({ name: 'clock', contract })
|
|
5887
|
-
* feature.ready().then(() => feature.send('timeUpdated', { time: Date.now() }))
|
|
5888
|
-
* feature.on('setTimezone', (data) => console.log(data))
|
|
5889
|
-
* ```
|
|
5890
|
-
*/
|
|
5891
3213
|
function createFeature(options) {
|
|
5892
3214
|
const contract = withControlContract(validateContract(options.contract));
|
|
5893
3215
|
const emitter = createEventEmitter();
|