ultravisor-beacon 0.0.2 → 0.0.4
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/package.json +3 -2
- package/source/Ultravisor-Beacon-AddressResolver.cjs +367 -0
- package/source/Ultravisor-Beacon-CapabilityAdapter.cjs +24 -9
- package/source/Ultravisor-Beacon-Client.cjs +25 -2
- package/source/Ultravisor-Beacon-ProviderRegistry.cjs +36 -0
- package/source/Ultravisor-Beacon-Service.cjs +93 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultravisor-beacon",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Ultravisor Beacon: lightweight beacon client and Fable service for remote task execution",
|
|
5
5
|
"main": "source/Ultravisor-Beacon-Service.cjs",
|
|
6
6
|
"scripts": {
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"author": "Steven Velozo <steven@velozo.com>",
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"fable-serviceproviderbase": "^3.0.19"
|
|
25
|
+
"fable-serviceproviderbase": "^3.0.19",
|
|
26
|
+
"ws": "^8.20.0"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"quackage": "^1.0.65"
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ultravisor Beacon Address Resolver
|
|
3
|
+
*
|
|
4
|
+
* Parses and resolves Universal Data Addresses — a URI scheme for
|
|
5
|
+
* referencing resources across a federated beacon mesh.
|
|
6
|
+
*
|
|
7
|
+
* Address format:
|
|
8
|
+
* >BeaconID/Context/Path...
|
|
9
|
+
*
|
|
10
|
+
* Components:
|
|
11
|
+
* > — Prefix indicating a beacon-scoped resource
|
|
12
|
+
* BeaconID — The registered beacon's name or ID
|
|
13
|
+
* Special values: '*' (any beacon with required capability),
|
|
14
|
+
* 'ULTRAVISOR' (the orchestrator itself)
|
|
15
|
+
* Context — The namespace within the beacon:
|
|
16
|
+
* 'File' — Filesystem access (content root)
|
|
17
|
+
* 'Staging' — Work item staging area
|
|
18
|
+
* 'Cache' — Cache storage (thumbnails, previews, etc.)
|
|
19
|
+
* 'Projection' — Data query endpoint (e.g. retold-facto)
|
|
20
|
+
* 'Operation' — Operation state/artifacts on the orchestrator
|
|
21
|
+
* Path — The resource path within that context
|
|
22
|
+
*
|
|
23
|
+
* Examples:
|
|
24
|
+
* >RR-BCN-001/File/volume3/Sort/SomeSong.mp3
|
|
25
|
+
* >ULTRAVISOR/Operation/0x732490df0/Stage/Transcoded.avi
|
|
26
|
+
* >RF-BCN-001/Projection/Countries/*
|
|
27
|
+
* >RF-BCN-001/Projection/Countries/FilteredTo/FBV~Name~LK~Col%25
|
|
28
|
+
* >WILDCARD/MediaConversion/Staging/input.jpg (use * for any beacon)
|
|
29
|
+
*
|
|
30
|
+
* This module is Fable-free — usable from both beacon clients and
|
|
31
|
+
* the Ultravisor server.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
class UltravisorBeaconAddressResolver
|
|
35
|
+
{
|
|
36
|
+
constructor(pOptions)
|
|
37
|
+
{
|
|
38
|
+
let tmpOptions = pOptions || {};
|
|
39
|
+
|
|
40
|
+
// Registry of known beacons and their contexts
|
|
41
|
+
// Map of BeaconID → { Contexts: { ContextName: { BasePath, BaseURL, Writable } } }
|
|
42
|
+
this._BeaconRegistry = {};
|
|
43
|
+
|
|
44
|
+
// Local beacon ID (set when running on a beacon)
|
|
45
|
+
this._LocalBeaconID = tmpOptions.LocalBeaconID || null;
|
|
46
|
+
|
|
47
|
+
// Local context mappings (set when running on a beacon)
|
|
48
|
+
// Map of ContextName → absolute filesystem path
|
|
49
|
+
this._LocalContextPaths = {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ================================================================
|
|
53
|
+
// Address Parsing
|
|
54
|
+
// ================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse a universal data address string into its components.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} pAddress - The address string (e.g. '>RR-BCN-001/File/photos/img.jpg')
|
|
60
|
+
* @returns {object|null} Parsed address: { BeaconID, Context, Path, Raw }
|
|
61
|
+
* Returns null if the address is not a valid universal address.
|
|
62
|
+
*/
|
|
63
|
+
parse(pAddress)
|
|
64
|
+
{
|
|
65
|
+
if (!pAddress || typeof pAddress !== 'string')
|
|
66
|
+
{
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Must start with >
|
|
71
|
+
if (pAddress.charAt(0) !== '>')
|
|
72
|
+
{
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Strip the prefix
|
|
77
|
+
let tmpBody = pAddress.substring(1);
|
|
78
|
+
|
|
79
|
+
// Split into segments: BeaconID / Context / Path...
|
|
80
|
+
let tmpFirstSlash = tmpBody.indexOf('/');
|
|
81
|
+
if (tmpFirstSlash < 0)
|
|
82
|
+
{
|
|
83
|
+
// Just a beacon ID with no context or path
|
|
84
|
+
return {
|
|
85
|
+
BeaconID: tmpBody,
|
|
86
|
+
Context: '',
|
|
87
|
+
Path: '',
|
|
88
|
+
Raw: pAddress
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let tmpBeaconID = tmpBody.substring(0, tmpFirstSlash);
|
|
93
|
+
let tmpRemainder = tmpBody.substring(tmpFirstSlash + 1);
|
|
94
|
+
|
|
95
|
+
let tmpSecondSlash = tmpRemainder.indexOf('/');
|
|
96
|
+
if (tmpSecondSlash < 0)
|
|
97
|
+
{
|
|
98
|
+
// BeaconID and Context, no path
|
|
99
|
+
return {
|
|
100
|
+
BeaconID: tmpBeaconID,
|
|
101
|
+
Context: tmpRemainder,
|
|
102
|
+
Path: '',
|
|
103
|
+
Raw: pAddress
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let tmpContext = tmpRemainder.substring(0, tmpSecondSlash);
|
|
108
|
+
let tmpPath = tmpRemainder.substring(tmpSecondSlash + 1);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
BeaconID: tmpBeaconID,
|
|
112
|
+
Context: tmpContext,
|
|
113
|
+
Path: tmpPath,
|
|
114
|
+
Raw: pAddress
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check if a string is a universal data address.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} pAddress - The string to check.
|
|
122
|
+
* @returns {boolean} True if the string starts with '>'.
|
|
123
|
+
*/
|
|
124
|
+
isUniversalAddress(pAddress)
|
|
125
|
+
{
|
|
126
|
+
return (typeof pAddress === 'string' && pAddress.length > 1 && pAddress.charAt(0) === '>');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Compose a universal data address from components.
|
|
131
|
+
*
|
|
132
|
+
* @param {string} pBeaconID - The beacon identifier.
|
|
133
|
+
* @param {string} pContext - The context namespace.
|
|
134
|
+
* @param {string} pPath - The resource path.
|
|
135
|
+
* @returns {string} The composed address.
|
|
136
|
+
*/
|
|
137
|
+
compose(pBeaconID, pContext, pPath)
|
|
138
|
+
{
|
|
139
|
+
let tmpParts = ['>', pBeaconID];
|
|
140
|
+
if (pContext)
|
|
141
|
+
{
|
|
142
|
+
tmpParts.push('/', pContext);
|
|
143
|
+
if (pPath)
|
|
144
|
+
{
|
|
145
|
+
tmpParts.push('/', pPath);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return tmpParts.join('');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ================================================================
|
|
152
|
+
// Beacon Registry
|
|
153
|
+
// ================================================================
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Register a beacon and its available contexts.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} pBeaconID - The beacon identifier.
|
|
159
|
+
* @param {object} pContexts - Map of ContextName → { BasePath, BaseURL, Writable, Description }
|
|
160
|
+
*/
|
|
161
|
+
registerBeacon(pBeaconID, pContexts)
|
|
162
|
+
{
|
|
163
|
+
this._BeaconRegistry[pBeaconID] = {
|
|
164
|
+
Contexts: pContexts || {}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Deregister a beacon.
|
|
170
|
+
*
|
|
171
|
+
* @param {string} pBeaconID - The beacon identifier to remove.
|
|
172
|
+
*/
|
|
173
|
+
deregisterBeacon(pBeaconID)
|
|
174
|
+
{
|
|
175
|
+
delete this._BeaconRegistry[pBeaconID];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get the context definition for a beacon.
|
|
180
|
+
*
|
|
181
|
+
* @param {string} pBeaconID - The beacon identifier.
|
|
182
|
+
* @param {string} pContext - The context name.
|
|
183
|
+
* @returns {object|null} The context definition, or null if not found.
|
|
184
|
+
*/
|
|
185
|
+
getBeaconContext(pBeaconID, pContext)
|
|
186
|
+
{
|
|
187
|
+
let tmpBeacon = this._BeaconRegistry[pBeaconID];
|
|
188
|
+
if (!tmpBeacon || !tmpBeacon.Contexts)
|
|
189
|
+
{
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return tmpBeacon.Contexts[pContext] || null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* List all registered beacons and their contexts.
|
|
197
|
+
*
|
|
198
|
+
* @returns {object} Map of BeaconID → { Contexts }
|
|
199
|
+
*/
|
|
200
|
+
listBeacons()
|
|
201
|
+
{
|
|
202
|
+
return JSON.parse(JSON.stringify(this._BeaconRegistry));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ================================================================
|
|
206
|
+
// Local Context Configuration
|
|
207
|
+
// ================================================================
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Set the local beacon identity (for resolving self-references).
|
|
211
|
+
*
|
|
212
|
+
* @param {string} pBeaconID - This beacon's ID.
|
|
213
|
+
*/
|
|
214
|
+
setLocalBeaconID(pBeaconID)
|
|
215
|
+
{
|
|
216
|
+
this._LocalBeaconID = pBeaconID;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Register a local context path mapping.
|
|
221
|
+
*
|
|
222
|
+
* @param {string} pContext - The context name (e.g. 'File', 'Staging', 'Cache').
|
|
223
|
+
* @param {string} pBasePath - The absolute filesystem path for this context.
|
|
224
|
+
*/
|
|
225
|
+
setLocalContextPath(pContext, pBasePath)
|
|
226
|
+
{
|
|
227
|
+
this._LocalContextPaths[pContext] = pBasePath;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ================================================================
|
|
231
|
+
// Address Resolution
|
|
232
|
+
// ================================================================
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Resolve a universal data address to a local filesystem path.
|
|
236
|
+
*
|
|
237
|
+
* Only works for addresses targeting the local beacon or addresses
|
|
238
|
+
* with explicit local context mappings. Returns null for remote addresses.
|
|
239
|
+
*
|
|
240
|
+
* @param {string} pAddress - The universal address string.
|
|
241
|
+
* @returns {object} Resolution result:
|
|
242
|
+
* { Local: true, Path: '/absolute/path/to/file' }
|
|
243
|
+
* or { Local: false, BeaconID: '...', Context: '...', Path: '...' }
|
|
244
|
+
* or { Error: 'description' }
|
|
245
|
+
*/
|
|
246
|
+
resolve(pAddress)
|
|
247
|
+
{
|
|
248
|
+
let tmpParsed = this.parse(pAddress);
|
|
249
|
+
if (!tmpParsed)
|
|
250
|
+
{
|
|
251
|
+
return { Error: `Invalid universal address: ${pAddress}` };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check if this targets the local beacon
|
|
255
|
+
let tmpIsLocal = false;
|
|
256
|
+
if (tmpParsed.BeaconID === this._LocalBeaconID)
|
|
257
|
+
{
|
|
258
|
+
tmpIsLocal = true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (tmpIsLocal)
|
|
262
|
+
{
|
|
263
|
+
let tmpBasePath = this._LocalContextPaths[tmpParsed.Context];
|
|
264
|
+
if (!tmpBasePath)
|
|
265
|
+
{
|
|
266
|
+
return { Error: `Unknown local context: ${tmpParsed.Context}` };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let tmpResolvedPath = tmpParsed.Path
|
|
270
|
+
? require('path').join(tmpBasePath, tmpParsed.Path)
|
|
271
|
+
: tmpBasePath;
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
Local: true,
|
|
275
|
+
Path: tmpResolvedPath,
|
|
276
|
+
BeaconID: tmpParsed.BeaconID,
|
|
277
|
+
Context: tmpParsed.Context
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Remote address — return the parsed components for the caller to handle
|
|
282
|
+
return {
|
|
283
|
+
Local: false,
|
|
284
|
+
BeaconID: tmpParsed.BeaconID,
|
|
285
|
+
Context: tmpParsed.Context,
|
|
286
|
+
Path: tmpParsed.Path
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Build a URL for accessing a resource on a remote beacon.
|
|
292
|
+
*
|
|
293
|
+
* Uses the beacon's registered BaseURL for the context.
|
|
294
|
+
*
|
|
295
|
+
* @param {string} pAddress - The universal address string.
|
|
296
|
+
* @returns {string|null} The full URL, or null if the beacon/context is unknown.
|
|
297
|
+
*/
|
|
298
|
+
resolveToURL(pAddress)
|
|
299
|
+
{
|
|
300
|
+
let tmpParsed = this.parse(pAddress);
|
|
301
|
+
if (!tmpParsed)
|
|
302
|
+
{
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let tmpContextDef = this.getBeaconContext(tmpParsed.BeaconID, tmpParsed.Context);
|
|
307
|
+
if (!tmpContextDef || !tmpContextDef.BaseURL)
|
|
308
|
+
{
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let tmpBaseURL = tmpContextDef.BaseURL;
|
|
313
|
+
// Ensure trailing slash on base
|
|
314
|
+
if (tmpBaseURL.charAt(tmpBaseURL.length - 1) !== '/')
|
|
315
|
+
{
|
|
316
|
+
tmpBaseURL += '/';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return tmpBaseURL + (tmpParsed.Path || '');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Scan an object's values for universal addresses and return
|
|
324
|
+
* a list of all addresses found. Useful for pre-processing
|
|
325
|
+
* operation settings to identify file transfer requirements.
|
|
326
|
+
*
|
|
327
|
+
* @param {object} pObject - The object to scan (e.g. work item Settings).
|
|
328
|
+
* @returns {Array<{ Key: string, Address: object }>} List of found addresses.
|
|
329
|
+
*/
|
|
330
|
+
scanForAddresses(pObject)
|
|
331
|
+
{
|
|
332
|
+
let tmpResults = [];
|
|
333
|
+
|
|
334
|
+
if (!pObject || typeof pObject !== 'object')
|
|
335
|
+
{
|
|
336
|
+
return tmpResults;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let tmpKeys = Object.keys(pObject);
|
|
340
|
+
for (let i = 0; i < tmpKeys.length; i++)
|
|
341
|
+
{
|
|
342
|
+
let tmpValue = pObject[tmpKeys[i]];
|
|
343
|
+
if (typeof tmpValue === 'string' && this.isUniversalAddress(tmpValue))
|
|
344
|
+
{
|
|
345
|
+
let tmpParsed = this.parse(tmpValue);
|
|
346
|
+
if (tmpParsed)
|
|
347
|
+
{
|
|
348
|
+
tmpResults.push({ Key: tmpKeys[i], Address: tmpParsed });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else if (typeof tmpValue === 'object' && tmpValue !== null)
|
|
352
|
+
{
|
|
353
|
+
// Recurse into nested objects
|
|
354
|
+
let tmpNested = this.scanForAddresses(tmpValue);
|
|
355
|
+
for (let j = 0; j < tmpNested.length; j++)
|
|
356
|
+
{
|
|
357
|
+
tmpNested[j].Key = tmpKeys[i] + '.' + tmpNested[j].Key;
|
|
358
|
+
tmpResults.push(tmpNested[j]);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return tmpResults;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
module.exports = UltravisorBeaconAddressResolver;
|
|
@@ -70,20 +70,35 @@ class UltravisorBeaconCapabilityAdapter extends libCapabilityProvider
|
|
|
70
70
|
let tmpDescriptorActions = (this._Descriptor && this._Descriptor.actions) ? this._Descriptor.actions : {};
|
|
71
71
|
let tmpActionDef = tmpDescriptorActions[pAction];
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
// Per-action Handler takes priority
|
|
74
|
+
if (tmpActionDef && typeof tmpActionDef.Handler === 'function')
|
|
74
75
|
{
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
try
|
|
77
|
+
{
|
|
78
|
+
return tmpActionDef.Handler(pWorkItem, pContext, fCallback, fReportProgress);
|
|
79
|
+
}
|
|
80
|
+
catch (pError)
|
|
81
|
+
{
|
|
82
|
+
return fCallback(pError);
|
|
83
|
+
}
|
|
77
84
|
}
|
|
78
85
|
|
|
79
|
-
|
|
86
|
+
// Fall back to descriptor-level execute method (Provider pattern —
|
|
87
|
+
// a single execute() that routes actions internally)
|
|
88
|
+
if (typeof this._Descriptor.execute === 'function')
|
|
80
89
|
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
try
|
|
91
|
+
{
|
|
92
|
+
return this._Descriptor.execute(pAction, pWorkItem, pContext, fCallback, fReportProgress);
|
|
93
|
+
}
|
|
94
|
+
catch (pError)
|
|
95
|
+
{
|
|
96
|
+
return fCallback(pError);
|
|
97
|
+
}
|
|
86
98
|
}
|
|
99
|
+
|
|
100
|
+
return fCallback(new Error(
|
|
101
|
+
`CapabilityAdapter "${this.Name}" has no Handler for action "${pAction}".`));
|
|
87
102
|
}
|
|
88
103
|
|
|
89
104
|
/**
|
|
@@ -397,10 +397,23 @@ class UltravisorBeaconClient
|
|
|
397
397
|
let tmpBody = {
|
|
398
398
|
Name: this._Config.Name,
|
|
399
399
|
Capabilities: this._Executor.providerRegistry.getCapabilities(),
|
|
400
|
+
ActionSchemas: this._Executor.providerRegistry.getActionSchemas(),
|
|
400
401
|
MaxConcurrent: this._Config.MaxConcurrent,
|
|
401
402
|
Tags: this._Config.Tags
|
|
402
403
|
};
|
|
403
404
|
|
|
405
|
+
// Include contexts if any are defined
|
|
406
|
+
if (this._Config.Contexts && Object.keys(this._Config.Contexts).length > 0)
|
|
407
|
+
{
|
|
408
|
+
tmpBody.Contexts = this._Config.Contexts;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Include operations if any are defined
|
|
412
|
+
if (Array.isArray(this._Config.Operations) && this._Config.Operations.length > 0)
|
|
413
|
+
{
|
|
414
|
+
tmpBody.Operations = this._Config.Operations;
|
|
415
|
+
}
|
|
416
|
+
|
|
404
417
|
this._httpRequest('POST', '/Beacon/Register', tmpBody, fCallback);
|
|
405
418
|
}
|
|
406
419
|
|
|
@@ -685,13 +698,23 @@ class UltravisorBeaconClient
|
|
|
685
698
|
console.log(`[Beacon] WebSocket connected to ${tmpWSURL}`);
|
|
686
699
|
|
|
687
700
|
// Register over WebSocket
|
|
688
|
-
|
|
701
|
+
let tmpWSRegPayload = {
|
|
689
702
|
Action: 'BeaconRegister',
|
|
690
703
|
Name: this._Config.Name,
|
|
691
704
|
Capabilities: this._Executor.providerRegistry.getCapabilities(),
|
|
705
|
+
ActionSchemas: this._Executor.providerRegistry.getActionSchemas(),
|
|
692
706
|
MaxConcurrent: this._Config.MaxConcurrent,
|
|
693
707
|
Tags: this._Config.Tags
|
|
694
|
-
}
|
|
708
|
+
};
|
|
709
|
+
if (this._Config.Contexts && Object.keys(this._Config.Contexts).length > 0)
|
|
710
|
+
{
|
|
711
|
+
tmpWSRegPayload.Contexts = this._Config.Contexts;
|
|
712
|
+
}
|
|
713
|
+
if (Array.isArray(this._Config.Operations) && this._Config.Operations.length > 0)
|
|
714
|
+
{
|
|
715
|
+
tmpWSRegPayload.Operations = this._Config.Operations;
|
|
716
|
+
}
|
|
717
|
+
this._wsSend(tmpWSRegPayload);
|
|
695
718
|
});
|
|
696
719
|
|
|
697
720
|
this._WebSocket.on('message', (pMessage) =>
|
|
@@ -129,6 +129,42 @@ class UltravisorBeaconProviderRegistry
|
|
|
129
129
|
return this._Capabilities.slice();
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Get serializable action schemas from all loaded providers.
|
|
134
|
+
*
|
|
135
|
+
* Collects action metadata (Description, SettingsSchema) from every
|
|
136
|
+
* registered provider. Handler functions are NOT included — the
|
|
137
|
+
* CapabilityAdapter's `actions` getter already strips them.
|
|
138
|
+
*
|
|
139
|
+
* @returns {Array<{ Capability: string, Action: string, Description: string, SettingsSchema: Array }>}
|
|
140
|
+
*/
|
|
141
|
+
getActionSchemas()
|
|
142
|
+
{
|
|
143
|
+
let tmpSchemas = [];
|
|
144
|
+
let tmpProviderNames = Object.keys(this._Providers);
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < tmpProviderNames.length; i++)
|
|
147
|
+
{
|
|
148
|
+
let tmpProvider = this._Providers[tmpProviderNames[i]];
|
|
149
|
+
let tmpActions = tmpProvider.actions || {};
|
|
150
|
+
let tmpActionNames = Object.keys(tmpActions);
|
|
151
|
+
|
|
152
|
+
for (let j = 0; j < tmpActionNames.length; j++)
|
|
153
|
+
{
|
|
154
|
+
let tmpActionDef = tmpActions[tmpActionNames[j]];
|
|
155
|
+
|
|
156
|
+
tmpSchemas.push({
|
|
157
|
+
Capability: tmpProvider.Capability,
|
|
158
|
+
Action: tmpActionNames[j],
|
|
159
|
+
Description: tmpActionDef.Description || '',
|
|
160
|
+
SettingsSchema: tmpActionDef.SettingsSchema || []
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return tmpSchemas;
|
|
166
|
+
}
|
|
167
|
+
|
|
132
168
|
/**
|
|
133
169
|
* Get all loaded providers.
|
|
134
170
|
*
|
|
@@ -30,6 +30,7 @@ const libBeaconClient = require('./Ultravisor-Beacon-Client.cjs');
|
|
|
30
30
|
const libCapabilityManager = require('./Ultravisor-Beacon-CapabilityManager.cjs');
|
|
31
31
|
const libConnectivityHTTP = require('./Ultravisor-Beacon-ConnectivityHTTP.cjs');
|
|
32
32
|
const libConnectivityWebSocket = require('./Ultravisor-Beacon-ConnectivityWebSocket.cjs');
|
|
33
|
+
const libAddressResolver = require('./Ultravisor-Beacon-AddressResolver.cjs');
|
|
33
34
|
|
|
34
35
|
class UltravisorBeaconService extends libFableServiceBase
|
|
35
36
|
{
|
|
@@ -49,14 +50,19 @@ class UltravisorBeaconService extends libFableServiceBase
|
|
|
49
50
|
PollIntervalMs: 5000,
|
|
50
51
|
HeartbeatIntervalMs: 30000,
|
|
51
52
|
StagingPath: '',
|
|
52
|
-
Tags: {}
|
|
53
|
+
Tags: {},
|
|
54
|
+
Contexts: {}
|
|
53
55
|
}, this.options || {});
|
|
54
56
|
|
|
55
57
|
// Internal components
|
|
56
58
|
this._CapabilityManager = new libCapabilityManager();
|
|
57
59
|
this._ConnectivityService = new libConnectivityHTTP(this.options);
|
|
60
|
+
this._AddressResolver = new libAddressResolver();
|
|
58
61
|
this._ThinClient = null;
|
|
59
62
|
this._Enabled = false;
|
|
63
|
+
|
|
64
|
+
// Operation definitions to register with the coordinator on connect
|
|
65
|
+
this._Operations = [];
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
// ================================================================
|
|
@@ -94,6 +100,43 @@ class UltravisorBeaconService extends libFableServiceBase
|
|
|
94
100
|
return this;
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Register an operation definition to push to the coordinator on connect.
|
|
105
|
+
*
|
|
106
|
+
* Operation definitions use the same graph structure as HypervisorState
|
|
107
|
+
* operations (Hash, Name, Description, Graph with Nodes and Connections).
|
|
108
|
+
* These are sent during beacon registration and stored by the coordinator.
|
|
109
|
+
*
|
|
110
|
+
* @param {object} pOperationDef - Operation definition:
|
|
111
|
+
* {
|
|
112
|
+
* Hash: 'video-ingest',
|
|
113
|
+
* Name: 'Video Ingest Pipeline',
|
|
114
|
+
* Description: '...',
|
|
115
|
+
* Graph: { Nodes: [...], Connections: [...] }
|
|
116
|
+
* }
|
|
117
|
+
* @returns {object} this (for chaining)
|
|
118
|
+
*/
|
|
119
|
+
registerOperation(pOperationDef)
|
|
120
|
+
{
|
|
121
|
+
if (!pOperationDef || typeof(pOperationDef) !== 'object')
|
|
122
|
+
{
|
|
123
|
+
if (this.log)
|
|
124
|
+
{
|
|
125
|
+
this.log.warn('UltravisorBeacon: registerOperation requires a valid object.');
|
|
126
|
+
}
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this._Operations.push(pOperationDef);
|
|
131
|
+
|
|
132
|
+
if (this.log)
|
|
133
|
+
{
|
|
134
|
+
this.log.info(`UltravisorBeacon: registered operation [${pOperationDef.Hash || 'auto'}] "${pOperationDef.Name || ''}"`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
|
|
97
140
|
/**
|
|
98
141
|
* Remove a previously registered capability.
|
|
99
142
|
*
|
|
@@ -116,6 +159,51 @@ class UltravisorBeaconService extends libFableServiceBase
|
|
|
116
159
|
return this._CapabilityManager.getCapabilityNames();
|
|
117
160
|
}
|
|
118
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Register a data context that this beacon exposes.
|
|
164
|
+
*
|
|
165
|
+
* Contexts define namespaces for the Universal Data Addressing scheme.
|
|
166
|
+
* For example, a retold-remote beacon might expose a 'File' context
|
|
167
|
+
* (content root) and a 'Cache' context (thumbnail cache).
|
|
168
|
+
*
|
|
169
|
+
* @param {string} pContextName - The context name (e.g. 'File', 'Cache', 'Staging').
|
|
170
|
+
* @param {object} pContextDef - Context definition:
|
|
171
|
+
* {
|
|
172
|
+
* BasePath: '/absolute/path', // Local filesystem path
|
|
173
|
+
* BaseURL: '/content/', // URL prefix for remote access
|
|
174
|
+
* Writable: true, // Whether remote writes are allowed
|
|
175
|
+
* Description: 'Content root' // Human-readable description
|
|
176
|
+
* }
|
|
177
|
+
* @returns {object} this (for chaining)
|
|
178
|
+
*/
|
|
179
|
+
registerContext(pContextName, pContextDef)
|
|
180
|
+
{
|
|
181
|
+
this.options.Contexts[pContextName] = pContextDef;
|
|
182
|
+
|
|
183
|
+
// Also register with the address resolver for local resolution
|
|
184
|
+
if (pContextDef.BasePath)
|
|
185
|
+
{
|
|
186
|
+
this._AddressResolver.setLocalContextPath(pContextName, pContextDef.BasePath);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (this.log)
|
|
190
|
+
{
|
|
191
|
+
this.log.info(`UltravisorBeacon: registered context [${pContextName}]`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get the address resolver instance.
|
|
199
|
+
*
|
|
200
|
+
* @returns {UltravisorBeaconAddressResolver}
|
|
201
|
+
*/
|
|
202
|
+
getAddressResolver()
|
|
203
|
+
{
|
|
204
|
+
return this._AddressResolver;
|
|
205
|
+
}
|
|
206
|
+
|
|
119
207
|
/**
|
|
120
208
|
* Enable beacon mode: build providers, create thin client, connect.
|
|
121
209
|
*
|
|
@@ -163,6 +251,8 @@ class UltravisorBeaconService extends libFableServiceBase
|
|
|
163
251
|
MaxConcurrent: this.options.MaxConcurrent || 1,
|
|
164
252
|
StagingPath: this.options.StagingPath || process.cwd(),
|
|
165
253
|
Tags: this.options.Tags || {},
|
|
254
|
+
Contexts: this.options.Contexts || {},
|
|
255
|
+
Operations: this._Operations.length > 0 ? this._Operations : undefined,
|
|
166
256
|
// Pass empty Providers array — we'll register adapters directly
|
|
167
257
|
Providers: []
|
|
168
258
|
});
|
|
@@ -196,6 +286,7 @@ class UltravisorBeaconService extends libFableServiceBase
|
|
|
196
286
|
}
|
|
197
287
|
|
|
198
288
|
this._Enabled = true;
|
|
289
|
+
this._AddressResolver.setLocalBeaconID(pBeacon.BeaconID);
|
|
199
290
|
|
|
200
291
|
if (this.log)
|
|
201
292
|
{
|
|
@@ -285,5 +376,6 @@ module.exports.CapabilityManager = libCapabilityManager;
|
|
|
285
376
|
module.exports.CapabilityAdapter = require('./Ultravisor-Beacon-CapabilityAdapter.cjs');
|
|
286
377
|
module.exports.CapabilityProvider = require('./Ultravisor-Beacon-CapabilityProvider.cjs');
|
|
287
378
|
module.exports.ProviderRegistry = require('./Ultravisor-Beacon-ProviderRegistry.cjs');
|
|
379
|
+
module.exports.AddressResolver = libAddressResolver;
|
|
288
380
|
module.exports.ConnectivityHTTP = libConnectivityHTTP;
|
|
289
381
|
module.exports.ConnectivityWebSocket = libConnectivityWebSocket;
|