dcp-client 5.0.4 → 5.1.1
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/.gitattributes +2 -0
- package/assets/dcp-client.css +16 -0
- package/assets/lucide/bug.svg +1 -0
- package/assets/lucide/circle-alert.svg +1 -0
- package/assets/lucide/hard-drive-upload.svg +1 -0
- package/assets/lucide/triangle-alert.svg +1 -0
- package/assets/x.svg +1 -0
- package/bin/dcp-config-value +2 -2
- package/bin/dcp-module-bundler +149 -0
- package/build/bundle +1 -1
- package/dcp-client.js +26 -13
- package/dist/dcp-client-bundle.js +1 -1
- package/dist/dcp-client-bundle.js.map +1 -1
- package/examples/nodejs/simple-job.js +3 -0
- package/index.js +359 -224
- package/index.py +1 -0
- package/lib/standaloneWorker.js +38 -3
- package/ns-map.js +1 -0
- package/os-basic.py +13 -0
- package/package.json +8 -3
- package/test-helpers/attempt-to-fetch.js +2 -2
- package/templates/alert-modal.html +0 -26
- package/templates/confirm-modal.html +0 -27
- package/templates/dcp-modal.css +0 -365
- package/templates/keystore-file-modal.html +0 -22
- package/templates/oauth-login-modal.html +0 -18
- package/templates/password-creation-modal.html +0 -29
- package/templates/password-entry-modal.html +0 -24
package/index.js
CHANGED
|
@@ -6,24 +6,23 @@
|
|
|
6
6
|
* same directory as this file, and inject the exported modules into the NodeJS
|
|
7
7
|
* module environment.
|
|
8
8
|
*
|
|
9
|
-
* There are three initialization styles provided, which are all basically the same,
|
|
9
|
+
* There are three initialization styles provided, which are all basically the same,
|
|
10
10
|
* have different calling styles;
|
|
11
11
|
* 1. initSync - blocks until initialization is complete
|
|
12
12
|
* 2. init - returns a Promise, is an "async" function; resolve when initialization is complete
|
|
13
13
|
* 3. initcb - invokes a callback when initialization is complete
|
|
14
14
|
*
|
|
15
|
-
* During initialization, we
|
|
16
|
-
* 2. build the layered dcp-config for the program invoking init
|
|
15
|
+
* During initialization, we
|
|
16
|
+
* 2. build the layered dcp-config for the program invoking init
|
|
17
17
|
* (see: https://people.kingsds.network/wesgarland/dcp-client-priorities.html /wg aug 2020)
|
|
18
18
|
* 3. download a new bundle if auto-update is on
|
|
19
19
|
* 4. make the bundle "see" the layered dcp-config as their global dcpConfig
|
|
20
|
-
* 5. re-inject the bundle modules (possibly from the new bundle)
|
|
20
|
+
* 5. re-inject the bundle modules (possibly from the new bundle)
|
|
21
21
|
*
|
|
22
22
|
* @author Wes Garland, wes@kingsds.network
|
|
23
23
|
* @date July 2019
|
|
24
24
|
*/
|
|
25
25
|
'use strict';
|
|
26
|
-
|
|
27
26
|
var reportErrors = true; /* can be overridden by options.reportErrors during init() */
|
|
28
27
|
var KVIN; /* KVIN context from internal kvin */
|
|
29
28
|
var XMLHttpRequest; /* from internal dcp-xhr */
|
|
@@ -40,7 +39,7 @@ const vm = require('vm');
|
|
|
40
39
|
const protectedDcpConfigKeys = [ 'system', 'bundle', 'worker', 'evaluator' ];
|
|
41
40
|
|
|
42
41
|
let initInvoked = false; /* flag to help us detect use of Compute API before init */
|
|
43
|
-
let originalDcpConfig = globalThis.dcpConfig ||
|
|
42
|
+
let originalDcpConfig = globalThis.dcpConfig || undefined; /* not undefined if user set their own dcpConfig global variable before init */
|
|
44
43
|
globalThis.dcpConfig = originalDcpConfig || { __filename };
|
|
45
44
|
|
|
46
45
|
const distDir = path.resolve(path.dirname(module.filename), 'dist');
|
|
@@ -49,15 +48,21 @@ const distDir = path.resolve(path.dirname(module.filename), 'dist');
|
|
|
49
48
|
* nodes of the right that we can expect to be overwritten by the registry.
|
|
50
49
|
*/
|
|
51
50
|
const bootstrapConfig = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
__bootstrapConfig: true,
|
|
52
|
+
bundleConfig: true,
|
|
53
|
+
scheduler: { location: new URL('http://bootstrap.distributed.computer/') },
|
|
54
|
+
bank: { location: new URL('http://bootstrap.distributed.computer/') },
|
|
55
|
+
packageManager: { location: new URL('http://bootstrap.distributed.computer/') },
|
|
56
|
+
portal: { location: new URL('http://bootstrap.distributed.computer/') },
|
|
57
|
+
pxAuth: { location: new URL('http://bootstrap.distributed.computer/') },
|
|
58
|
+
oAuth: { location: new URL('http://bootstrap.distributed.computer/') },
|
|
59
|
+
cdn: { location: new URL('http://bootstrap.distributed.computer/') },
|
|
60
|
+
worker: {},
|
|
61
|
+
global: {},
|
|
62
|
+
dcp: {},
|
|
58
63
|
};
|
|
59
64
|
|
|
60
|
-
const
|
|
65
|
+
const bundleScope = {
|
|
61
66
|
URL,
|
|
62
67
|
URLSearchParams,
|
|
63
68
|
// GPU,
|
|
@@ -90,31 +95,49 @@ const bundleSandbox = {
|
|
|
90
95
|
window: globalThis,
|
|
91
96
|
};
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Run code with symbols injected into the function scope.
|
|
101
|
+
* @param {Object} symbols An object whose properties are names of symbols which will be available
|
|
102
|
+
* in the evaluation scope; the symbol values are the object values.
|
|
103
|
+
*/
|
|
104
|
+
function runCodeWithSymbols(symbols, code, options)
|
|
94
105
|
{
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
106
|
+
const wrapperFunArgs = Object.keys(symbols);
|
|
107
|
+
const invokeArgs = Object.values(symbols);
|
|
108
|
+
const wrappedCode = `"use strict";\n(function __rcws_wrapper(${wrapperFunArgs.join(',')}) { ${code} })`;
|
|
109
|
+
|
|
110
|
+
options = Object.assign({ columnOffset: Number(options?.columnOffset) + 12 }, options);
|
|
111
|
+
const script = new vm.Script(wrappedCode, options);
|
|
112
|
+
const rcwsWrapper = script.runInThisContext(options);
|
|
113
|
+
return rcwsWrapper.apply(globalThis, invokeArgs);
|
|
98
114
|
}
|
|
99
115
|
|
|
100
|
-
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
116
|
+
/**
|
|
117
|
+
* Evaluate a file inside a namespace-protecting IIFE; similar the new vm contexts used for configury,
|
|
118
|
+
* but using the current context so that Object literals are instances of this context's Object.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} filename The name of a file which contains a single JS expression
|
|
121
|
+
* @param {object} gsymbox An object which simulates the global object via symbol collision; any
|
|
122
|
+
* symbols which don't collide are resolved up the scope chain against this
|
|
123
|
+
* context's global object.
|
|
124
|
+
* @param {object} options Options to pass to vm.runInThisContext()
|
|
125
|
+
*
|
|
126
|
+
* @returns the value of the expression in the file
|
|
103
127
|
*/
|
|
104
|
-
function
|
|
128
|
+
function evalBundleCodeInIIFE(code, gsymbox, options)
|
|
105
129
|
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
debug('dcp-client:
|
|
114
|
-
throw e
|
|
115
|
-
}
|
|
130
|
+
const prologue = '(function __dynamic_evalBundle__IIFE(' + Object.keys(gsymbox).join(',') + '){ return ';
|
|
131
|
+
const epilogue = '\n});';
|
|
132
|
+
|
|
133
|
+
options = Object.assign({ filename: '(eval code)', lineOffset: 0 }, options);
|
|
134
|
+
if (options?.filename)
|
|
135
|
+
debug('dcp-client:evalBundle')('evaluating file', options.filename);
|
|
136
|
+
else
|
|
137
|
+
debug('dcp-client:evalBundle')(`evaluating code ${code.slice(0,40).replace('\n', ' ')}...`);
|
|
116
138
|
|
|
117
|
-
|
|
139
|
+
const fun = vm.runInThisContext(prologue + code + epilogue, options);
|
|
140
|
+
return fun.apply(null, Object.values(gsymbox));
|
|
118
141
|
}
|
|
119
142
|
|
|
120
143
|
/**
|
|
@@ -122,66 +145,44 @@ function evalScriptInSandbox(filename, sandbox)
|
|
|
122
145
|
* but using the current context so that Object literals are instances of this context's Object.
|
|
123
146
|
*
|
|
124
147
|
* @param {string} filename The name of a file which contains a single JS expression
|
|
125
|
-
* @param {object}
|
|
148
|
+
* @param {object} gsymbox An object which simulates the global object via symbol collision; any
|
|
126
149
|
* symbols which don't collide are resolved up the scope chain against this
|
|
127
150
|
* context's global object.
|
|
128
151
|
* @returns the value of the expression in the file
|
|
129
152
|
*/
|
|
130
|
-
function
|
|
153
|
+
function evalBundleFileInIIFE(filename, gsymbox)
|
|
131
154
|
{
|
|
132
|
-
const prologue = '(function __dynamic_evalFile__IIFE(' + Object.keys(sandbox).join(',') + '){ return ';
|
|
133
|
-
const epilogue = '\n});';
|
|
134
|
-
const options = { filename, lineOffset: 0 };
|
|
135
|
-
|
|
136
|
-
debug('dcp-client:evalFileInIIFE')('evaluating', filename);
|
|
137
155
|
const fileContents = fs.readFileSync(path.resolve(distDir, filename), 'utf8');
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return fun.apply(null, Object.values(sandbox));
|
|
156
|
+
return evalBundleCodeInIIFE(fileContents, gsymbox, { filename });
|
|
141
157
|
}
|
|
142
158
|
|
|
143
|
-
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* 'code' must come from a trusted source, so we don't execute unknown code.
|
|
159
|
+
/**
|
|
160
|
+
* Evaluate a config file (eg. dcp-config fragment) in a function scope of the current context that has
|
|
161
|
+
* extra symbols injected for use by the config file.
|
|
147
162
|
*
|
|
148
|
-
* @param
|
|
149
|
-
* @param sandbox {object} A sandbox object, used for injecting 'global' symbols as needed
|
|
163
|
+
* @param symbols {object} An object used for injecting 'global' symbols as needed
|
|
150
164
|
* @param filename {string} The name of the file we're evaluating for stack-
|
|
151
165
|
* trace purposes.
|
|
152
166
|
*/
|
|
153
|
-
function
|
|
167
|
+
function evalConfigFile(symbols, filename)
|
|
154
168
|
{
|
|
155
|
-
var
|
|
169
|
+
var code = fs.readFileSync(filename, 'utf-8');
|
|
156
170
|
const codeHasVeryLongLine = Boolean(/[^\n]{1000,}[^\n]*\n/.test(code));
|
|
157
171
|
const runOptions = {
|
|
158
172
|
filename,
|
|
159
173
|
lineOffset: 0,
|
|
160
|
-
columnOffset:
|
|
174
|
+
columnOffset: 0,
|
|
161
175
|
displayErrors: !codeHasVeryLongLine
|
|
162
176
|
};
|
|
163
177
|
|
|
164
|
-
|
|
165
|
-
* parser to figure out which syntax this code is in. In the case of a produced value, we use the
|
|
166
|
-
* last value which was evaluated by the engine as the result, just like eval.
|
|
167
|
-
*/
|
|
168
|
-
try
|
|
169
|
-
{
|
|
170
|
-
result = runSandboxedCode(sandbox, '"use strict";' + code, runOptions);
|
|
171
|
-
}
|
|
172
|
-
catch(error)
|
|
178
|
+
if (withoutComments(code).match(/^\s*{/)) /* config file is just a JS object literal */
|
|
173
179
|
{
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (!nodejsErrorMessage.test(error.message) && !bunErrorMessage.test(error.message))
|
|
177
|
-
throw error;
|
|
178
|
-
|
|
179
|
-
code = `(() => {;${code}})()`; /* wrap in IIFE so conf can return objects */
|
|
180
|
-
runOptions.columnOffset += 9;
|
|
181
|
-
result = runSandboxedCode(sandbox, '"use strict";' + code, runOptions);
|
|
180
|
+
code = `return (${code})`;
|
|
181
|
+
runOptions.columnOffset = 8;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
debug('dcp-client:evalConfigFile')('evaluating config file', runOptions.filename);
|
|
185
|
+
return runCodeWithSymbols(symbols, code, runOptions);
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
/**
|
|
@@ -193,26 +194,27 @@ function withoutComments(code) {
|
|
|
193
194
|
return code.replace(/(\/\*([\s\S]*?)\*\/)|(\/\/(.*)$)/gm, '')
|
|
194
195
|
}
|
|
195
196
|
|
|
196
|
-
/**
|
|
197
|
+
/**
|
|
197
198
|
* Load the bootstrap bundle - used primarily to plumb in utils::justFetch.
|
|
198
|
-
* Runs in a different, but identical,
|
|
199
|
-
*
|
|
200
|
-
*
|
|
199
|
+
* Runs in a different, but identical, scope the config files and client code. This code is evaluated
|
|
200
|
+
* with the bootstrap config as dcpConfig so that static initialization of dcpConfig in any of the
|
|
201
|
+
* bootstrap modules can mutate the bottom-most layer of the dcpConfig stack.
|
|
201
202
|
*/
|
|
202
203
|
function loadBootstrapBundle() {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
204
|
+
const bundleFilename = path.resolve(distDir, 'dcp-client-bundle.js');
|
|
205
|
+
const scope = Object.assign(bundleScope, { dcpConfig: bootstrapConfig });
|
|
206
|
+
|
|
207
|
+
scope.globalThis = scope;
|
|
208
|
+
scope.window = scope;
|
|
209
|
+
scope.dcpConfig = bootstrapConfig;
|
|
210
|
+
|
|
211
|
+
debug('dcp-client:bundle')(' - loading bootstrap bundle');
|
|
212
|
+
return evalBundleFileInIIFE(bundleFilename, Object.assign({}, bundleScope));
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
const injectedModules = {};
|
|
214
216
|
const resolveFilenamePrevious = moduleSystem._resolveFilename;
|
|
215
|
-
moduleSystem._resolveFilename = function dcpClient$$injectModule$resolveFilenameShim(moduleIdentifier) {
|
|
217
|
+
moduleSystem._resolveFilename = function dcpClient$$injectModule$resolveFilenameShim(moduleIdentifier) {
|
|
216
218
|
if (injectedModules.hasOwnProperty(moduleIdentifier)) {
|
|
217
219
|
if (!initInvoked) {
|
|
218
220
|
if( moduleIdentifier === 'dcp/compute') throw new Error(`module ${moduleIdentifier} cannot be required until the dcp-client::init() promise has been resolved.`);
|
|
@@ -222,8 +224,8 @@ moduleSystem._resolveFilename = function dcpClient$$injectModule$resolveFilename
|
|
|
222
224
|
|
|
223
225
|
return resolveFilenamePrevious.apply(null, arguments)
|
|
224
226
|
}
|
|
225
|
-
/**
|
|
226
|
-
* Inject an initialized module into the native NodeJS module system.
|
|
227
|
+
/**
|
|
228
|
+
* Inject an initialized module into the native NodeJS module system.
|
|
227
229
|
*
|
|
228
230
|
* @param id {string} module identifier
|
|
229
231
|
* @param moduleExports {object} the module's exports object
|
|
@@ -247,7 +249,7 @@ function injectModule(id, moduleExports, clobber) {
|
|
|
247
249
|
/**
|
|
248
250
|
* Inject modules from a bundle according to a namespace map.
|
|
249
251
|
* The namespace map maps bundle exports onto the internal require(dcp/...) namespace.
|
|
250
|
-
*
|
|
252
|
+
*
|
|
251
253
|
* @param nsMap the namespace map object
|
|
252
254
|
* @param bundle the webpack bundle (~moduleGroup object)
|
|
253
255
|
* @param clobber {boolean} inject on top of an existing module identifier
|
|
@@ -255,7 +257,7 @@ function injectModule(id, moduleExports, clobber) {
|
|
|
255
257
|
*/
|
|
256
258
|
function injectNsMapModules(nsMap, bundle, bundleLabel, clobber) {
|
|
257
259
|
bundle = Object.assign({}, bundle);
|
|
258
|
-
|
|
260
|
+
|
|
259
261
|
for (let moduleId in nsMap)
|
|
260
262
|
{
|
|
261
263
|
let moduleExports = bundle[nsMap[moduleId]];
|
|
@@ -290,53 +292,143 @@ injectNsMapModules(require('./ns-map'), loadBootstrapBundle(), 'bootstrap');
|
|
|
290
292
|
injectModule('dcp/bootstrap-build', require('dcp/build'));
|
|
291
293
|
|
|
292
294
|
KVIN = new (require('dcp/internal/kvin')).KVIN();
|
|
295
|
+
const bootstrapClasses = {
|
|
296
|
+
DcpURL: require('dcp/dcp-url').DcpURL,
|
|
297
|
+
Address: require('dcp/wallet').Address,
|
|
298
|
+
};
|
|
293
299
|
KVIN.userCtors.dcpUrl$$DcpURL = require('dcp/dcp-url').DcpURL;
|
|
294
300
|
KVIN.userCtors.dcpEth$$Address = require('dcp/wallet').Address;
|
|
295
301
|
|
|
296
|
-
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
302
|
+
/**
|
|
303
|
+
* Merge a new configuration object on top of an existing one. The new object is overlaid on the
|
|
304
|
+
* existing object, so that own properties specified in the existing object graph overwrite, but
|
|
305
|
+
* unspecified edges are left alone.
|
|
306
|
+
*
|
|
307
|
+
* Instances of URL and dcpUrl::URL receive special treatment: if they are being overwritten by a
|
|
308
|
+
* string, the string is used the argument to the constructor to create a new object that replaces
|
|
309
|
+
* the entire value.
|
|
310
|
+
*
|
|
311
|
+
* Arrays are concatenated together when merging. In the case where an Array and an Object are merged,
|
|
312
|
+
* the result will be an Array and it will be merged with the object's values.
|
|
313
|
+
*
|
|
314
|
+
* Objects not mentioned above whose constructors are neither Function nor Object are treated as though
|
|
315
|
+
* they are primitive values, as they may contain internal state that is not represented solely by
|
|
316
|
+
* their own properties.
|
|
299
317
|
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* to create a new object that replaces the entire value.
|
|
303
|
-
*
|
|
304
|
-
* *note* - We treat objects with constructors other than Function or Object
|
|
305
|
-
* as though they were primitive values, as they may contain internal
|
|
306
|
-
* state that is not represented solely by their own properties
|
|
318
|
+
* It is possible to "merge" Objects with Arrays. We basically decide that the Array indicies have no
|
|
319
|
+
* special meaning and neither do the own property names.
|
|
307
320
|
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
321
|
+
* {} -> {} => leaf merge
|
|
322
|
+
* [] -> [] => concat except it leaves out duplicate elements
|
|
323
|
+
* {} -> [] => Object.entries({}) -> [] => concat except it leaves out duplicate elements onto array
|
|
324
|
+
* [] -> {} => [] -> Object with dynamic keys => merge into object
|
|
325
|
+
*
|
|
326
|
+
* @param {object} existing Top node of an object graph whose edges may be replaced
|
|
327
|
+
* @param {object|undefined} neo Top node of an object graph whose edges describe the replacement
|
|
312
328
|
*/
|
|
313
|
-
|
|
329
|
+
globalThis.addConfig = addConfig;
|
|
330
|
+
function addConfig (existing, neo, dotPath)
|
|
331
|
+
{
|
|
314
332
|
const { DcpURL } = require('dcp/dcp-url');
|
|
315
333
|
|
|
334
|
+
if (neo === undefined || neo === existing)
|
|
335
|
+
return;
|
|
316
336
|
debug('dcp-client:config-verbose')('adding', neo);
|
|
337
|
+
if (typeof neo !== 'object')
|
|
338
|
+
throw new TypeError(`Unable to merge ${typeof neo} value '${neo}' into ${nodeName()}`);
|
|
317
339
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
340
|
+
function isIntrinsic(val)
|
|
341
|
+
{
|
|
342
|
+
return (val === null || (typeof val !== 'object' && typeof val !== 'function'))
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function nodeName(edge)
|
|
346
|
+
{
|
|
347
|
+
if (edge)
|
|
348
|
+
return dotPath ? `${dotPath}.${edge}` : edge;
|
|
349
|
+
else
|
|
350
|
+
return dotPath || 'dcpConfig';
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Make an object from an array so that it can be merged into another object without key collision */
|
|
354
|
+
function objFromArrForObj(arr, obj)
|
|
355
|
+
{
|
|
356
|
+
const numericKeys = Object.keys(arr).map(key => Number(key)).filter(key => !isNaN(key))
|
|
357
|
+
const maxNumericKey = numericKeys.length ? numericKeys[numericKeys.length - 1] : NaN;
|
|
358
|
+
const nonNumericKeys = Object.keys(arr).filter(key => isNaN(Number(key)));
|
|
359
|
+
const tmp = {};
|
|
360
|
+
|
|
361
|
+
for (let key of nonNumericKeys)
|
|
362
|
+
tmp[key] = arr[key];
|
|
363
|
+
for (let idx of numericKeys)
|
|
364
|
+
tmp[idx + maxNumericKey] = arr[idx];
|
|
365
|
+
return tmp;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
for (const prop of Object.getOwnPropertyNames(neo))
|
|
369
|
+
{
|
|
370
|
+
if (false
|
|
371
|
+
|| !Object.hasOwnProperty.call(existing, prop) /* no collision? use new */
|
|
372
|
+
|| existing[prop] === neo[prop]
|
|
373
|
+
|| isIntrinsic(neo[prop]) /* instrinsic? use new */
|
|
374
|
+
|| isIntrinsic(existing[prop])
|
|
375
|
+
|| neo[prop] instanceof URL /* new URLs overwrite old nodes */
|
|
376
|
+
|| DcpURL.isURL(neo[prop])
|
|
377
|
+
|| neo[prop].constructor === RegExp /* new RegExps overwrite old nodes */
|
|
378
|
+
)
|
|
379
|
+
{
|
|
380
|
+
existing[prop] = neo[prop];
|
|
324
381
|
continue;
|
|
325
382
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
383
|
+
|
|
384
|
+
if (typeof neo[prop] !== 'object')
|
|
385
|
+
throw new TypeError(`Unable to merge ${typeof neo[prop]} value into ${nodeName(prop)}`);
|
|
386
|
+
else
|
|
387
|
+
{
|
|
388
|
+
switch(neo[prop].constructor)
|
|
389
|
+
{
|
|
390
|
+
case RegExp: case Object: case DcpURL: case URL: case Array:
|
|
391
|
+
case bootstrapClasses.DcpURL: case bootstrapClasses.Address:
|
|
392
|
+
break;
|
|
393
|
+
case Function: /* previously supported: do we really need this? /wg May 2025 */
|
|
394
|
+
default:
|
|
395
|
+
throw new TypeError(`Unable to merge ${neo[prop].constructor.name} object into ${nodeName(prop)}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
let neoOfProp = neo[prop]; /* !!! do not use neo[prop] below here !!! */
|
|
400
|
+
|
|
401
|
+
if (typeof existing !== 'object' || (existing[prop].constructor !== Object && existing[prop].constructor !== Array))
|
|
402
|
+
throw new TypeError(`Unable to merge into ${existing[prop].constructor?.name || typeof existing} value of ${nodeName(prop)}`);
|
|
403
|
+
if (typeof neoOfProp !== 'object' || (neoOfProp.constructor !== Object && neoOfProp.constructor !== Array))
|
|
404
|
+
throw new TypeError(`Unable to merge ${neoOfProp.constructor?.name || typeof neoOfProp} value into ${nodeName(prop)}`);
|
|
405
|
+
|
|
406
|
+
/* When one of the objects is an array and the other is a plain object, we transform the new one to
|
|
407
|
+
* match the existing one.
|
|
408
|
+
*/
|
|
409
|
+
if (!Array.isArray(neoOfProp) && Array.isArray(existing[prop])) /* {} -> [] */
|
|
410
|
+
neoOfProp = Object.entries(neoOfProp);
|
|
411
|
+
else if (Array.isArray(neoOfProp) && !Array.isArray(existing[prop])) /* [] -> {} */
|
|
412
|
+
neoOfProp = objFromArrForObj(neoOfProp, existing[prop]);
|
|
413
|
+
|
|
414
|
+
/* Either merge objects or arrays. Objects collide props, Arrays collide exactly equal values. */
|
|
415
|
+
if (!Array.isArray(existing[prop]))
|
|
416
|
+
addConfig(existing[prop], neoOfProp, nodeName(prop));
|
|
417
|
+
else
|
|
418
|
+
{
|
|
419
|
+
/* concat new array at end of old array, omitting values which are already in the array */
|
|
420
|
+
for (let value of neoOfProp)
|
|
421
|
+
{
|
|
422
|
+
if (!existing[prop].includes(value))
|
|
423
|
+
existing[prop].push(value);
|
|
329
424
|
}
|
|
330
|
-
addConfig(existing[prop], neo[prop])
|
|
331
|
-
} else {
|
|
332
|
-
existing[prop] = neo[prop]
|
|
333
425
|
}
|
|
334
426
|
}
|
|
335
427
|
}
|
|
336
428
|
|
|
337
429
|
/**
|
|
338
430
|
* Returns a graph of empty objects with the same edges as the passed-in node. Only base Objects are
|
|
339
|
-
* considered, not instances of derived classes (like URL). The newly-created nodes inherit from their
|
|
431
|
+
* considered, not instances of derived classes (like URL). The newly-created nodes inherit from their
|
|
340
432
|
* equivalent nodes. The net effect is that the returned graph can have its nodes read like usual, but
|
|
341
433
|
* writes "stick" to the new nodes instead of modifying the original node.
|
|
342
434
|
*
|
|
@@ -371,7 +463,7 @@ function magicView(node, seen)
|
|
|
371
463
|
|
|
372
464
|
/**
|
|
373
465
|
* Throw an exception if the given fullPath is not a "safe" file to load.
|
|
374
|
-
* "Safe" files are those that are unlikely to contain malicious code, as
|
|
466
|
+
* "Safe" files are those that are unlikely to contain malicious code, as
|
|
375
467
|
* they are owned by an administrator or the same person who loaded the
|
|
376
468
|
* code.
|
|
377
469
|
*
|
|
@@ -397,7 +489,7 @@ function checkConfigFileSafePerms(fullPath, statBuf)
|
|
|
397
489
|
fun.mainStat = require.main ? fs.statSync(require.main.filename) : {};
|
|
398
490
|
|
|
399
491
|
statBuf = fs.statSync(fullPath);
|
|
400
|
-
|
|
492
|
+
|
|
401
493
|
/* Disallow files with world-writeable path components. @todo reduce redundant checks */
|
|
402
494
|
if (os.platform() !== 'win32')
|
|
403
495
|
{
|
|
@@ -428,7 +520,7 @@ function checkConfigFileSafePerms(fullPath, statBuf)
|
|
|
428
520
|
return true; /* conf and program in same group */
|
|
429
521
|
if ((fun.mainStat.mode & 0o020) && (statBuf.gid === process.getgid()))
|
|
430
522
|
return true; /* program is group-writeable and we are in group */
|
|
431
|
-
|
|
523
|
+
|
|
432
524
|
throw new Error('did not load configuration file due to invalid permissions: ' + fullPath);
|
|
433
525
|
}
|
|
434
526
|
|
|
@@ -448,19 +540,19 @@ function addConfigFile(existing /*, file path components ... */) {
|
|
|
448
540
|
fullPath = path.join(fullPath, arguments[i]);
|
|
449
541
|
}
|
|
450
542
|
const fpSnap = fullPath;
|
|
451
|
-
|
|
543
|
+
|
|
452
544
|
/**
|
|
453
545
|
* Make a the global object for this context this config file is evaluated in.
|
|
454
|
-
* - Top-level keys from dcpConfig become properties of this object, so that we can write statements
|
|
546
|
+
* - Top-level keys from dcpConfig become properties of this object, so that we can write statements
|
|
455
547
|
* like scheduler.location='XXX' in the file.
|
|
456
548
|
* - A variable named `dcpConfig` is also added, so that we could replace nodes wholesale, eg
|
|
457
549
|
* `dcpConfig.scheduler = { location: 'XXX' }`.
|
|
458
550
|
* - A require object that resolves relative to the config file is injected
|
|
459
551
|
* - All of the globals that we use for evaluating the bundle are also injected
|
|
460
552
|
*/
|
|
461
|
-
function
|
|
553
|
+
function makeConfigFileSymbols()
|
|
462
554
|
{
|
|
463
|
-
var
|
|
555
|
+
var configFileScope = Object.assign({}, bundleScope, {
|
|
464
556
|
dcpConfig: existing,
|
|
465
557
|
require: moduleSystem.createRequire(fullPath),
|
|
466
558
|
url: (href) => new (require('dcp/dcp-url').DcpURL)(href),
|
|
@@ -469,13 +561,13 @@ function addConfigFile(existing /*, file path components ... */) {
|
|
|
469
561
|
});
|
|
470
562
|
|
|
471
563
|
for (let key in existing)
|
|
472
|
-
if (!
|
|
473
|
-
|
|
564
|
+
if (!configFileScope.hasOwnProperty(key))
|
|
565
|
+
configFileScope[key] = configFileScope.dcpConfig[key];
|
|
474
566
|
|
|
475
|
-
assert(
|
|
476
|
-
return
|
|
567
|
+
assert(configFileScope.console);
|
|
568
|
+
return configFileScope;
|
|
477
569
|
}
|
|
478
|
-
|
|
570
|
+
|
|
479
571
|
if (fullPath && checkConfigFileSafePerms(fullPath + '.json'))
|
|
480
572
|
{
|
|
481
573
|
fullPath = fullPath + './json';
|
|
@@ -496,17 +588,7 @@ function addConfigFile(existing /*, file path components ... */) {
|
|
|
496
588
|
{
|
|
497
589
|
fullPath = fullPath + '.js';
|
|
498
590
|
debug('dcp-client:config')(` * Loading configuration from ${fullPath}`);
|
|
499
|
-
|
|
500
|
-
const configSandbox = makeConfigSandbox();
|
|
501
|
-
const code = fs.readFileSync(fullPath, 'utf-8');
|
|
502
|
-
let neo;
|
|
503
|
-
|
|
504
|
-
if (withoutComments(code).match(/^\s*{/)) /* config file is just a JS object literal */
|
|
505
|
-
neo = evalStringInSandbox(`return (${code});`, configSandbox, fullPath);
|
|
506
|
-
else
|
|
507
|
-
neo = evalStringInSandbox(code, configSandbox, fullPath);
|
|
508
|
-
|
|
509
|
-
addConfig(existing, neo);
|
|
591
|
+
addConfig(existing, evalConfigFile(makeConfigFileSymbols(), fullPath));
|
|
510
592
|
return fullPath;
|
|
511
593
|
}
|
|
512
594
|
|
|
@@ -540,7 +622,7 @@ function coerceMagicRegProps(o, seen)
|
|
|
540
622
|
}
|
|
541
623
|
|
|
542
624
|
/** Merge a new configuration object on top of an existing one, via
|
|
543
|
-
* addConfig(). The registry key is read, turned into an object, and
|
|
625
|
+
* addConfig(). The registry key is read, turned into an object, and
|
|
544
626
|
* becomes the neo config.
|
|
545
627
|
*/
|
|
546
628
|
async function addConfigRKey(existing, hive, keyTail) {
|
|
@@ -564,7 +646,7 @@ async function addConfigRKey(existing, hive, keyTail) {
|
|
|
564
646
|
function addConfigEnv(existing, prefix) {
|
|
565
647
|
var re = new RegExp('^' + prefix);
|
|
566
648
|
var neo = {};
|
|
567
|
-
|
|
649
|
+
|
|
568
650
|
for (let v in process.env) {
|
|
569
651
|
if (!process.env.hasOwnProperty(v) || !v.match(re)) {
|
|
570
652
|
continue
|
|
@@ -600,12 +682,12 @@ function fixCase(ugly)
|
|
|
600
682
|
}
|
|
601
683
|
|
|
602
684
|
/**
|
|
603
|
-
* Patch up an object graph to fix up minor class instance issues. For example, if we get a
|
|
604
|
-
* internal bundle and then download a new
|
|
605
|
-
* won't benefit from any bug fixes, new functionality, etc.
|
|
685
|
+
* Patch up an object graph to fix up minor class instance issues. For example, if we get a DcpURL from
|
|
686
|
+
* the internal bundle and then download a new DcpURL class, it won't be an instance of the new class
|
|
687
|
+
* and it won't benefit from any bug fixes, new functionality, etc.
|
|
606
688
|
*
|
|
607
689
|
* @param {object} patchupList a mapping which tells us how to fix these problems for specific
|
|
608
|
-
* classes. This map is an array with each element having the shape
|
|
690
|
+
* classes. This map is an array with each element having the shape
|
|
609
691
|
* { how, right, wrong }.
|
|
610
692
|
*
|
|
611
693
|
* right - the right constructor to use
|
|
@@ -614,6 +696,8 @@ function fixCase(ugly)
|
|
|
614
696
|
*/
|
|
615
697
|
function patchupClasses(patchupList, o, seen)
|
|
616
698
|
{
|
|
699
|
+
const pucKVIN = new (require('dcp/internal/kvin')).KVIN();
|
|
700
|
+
|
|
617
701
|
/* seen list keeps us from blowing the stack on graphs with cycles */
|
|
618
702
|
if (!seen)
|
|
619
703
|
seen = [];
|
|
@@ -632,12 +716,18 @@ function patchupClasses(patchupList, o, seen)
|
|
|
632
716
|
if (typeof o[key] !== 'object' || o[key] === null || (Object.getPrototypeOf(o[key]) !== patchupList[i].wrong.prototype))
|
|
633
717
|
continue;
|
|
634
718
|
assert(patchupList[i].wrong !== patchupList[i].right);
|
|
635
|
-
|
|
719
|
+
|
|
636
720
|
switch (patchupList[i].how)
|
|
637
721
|
{
|
|
638
722
|
case 'kvin':
|
|
639
|
-
|
|
723
|
+
{
|
|
724
|
+
const className = o[key].constructor.name;
|
|
725
|
+
pucKVIN.userCtors[className] = patchupList[i].wrong;
|
|
726
|
+
const tmp = pucKVIN.marshal(o[key]);
|
|
727
|
+
pucKVIN.userCtors[o[key].constructor.name] = patchupList[i].right;
|
|
728
|
+
o[key] = pucKVIN.unmarshal(tmp);
|
|
640
729
|
break;
|
|
730
|
+
}
|
|
641
731
|
case 'ctorStr':
|
|
642
732
|
o[key] = new (patchupList[i].right)(String(o[key]));
|
|
643
733
|
break;
|
|
@@ -657,6 +747,7 @@ function patchupClasses(patchupList, o, seen)
|
|
|
657
747
|
throw new Error(`unknown patchup method ${patchupList[i].how}`);
|
|
658
748
|
}
|
|
659
749
|
|
|
750
|
+
assert(o[key].constructor === patchupList[i].right);
|
|
660
751
|
moreTraverse = false; /* don't patch up props of stuff we've patched up */
|
|
661
752
|
break;
|
|
662
753
|
}
|
|
@@ -666,7 +757,7 @@ function patchupClasses(patchupList, o, seen)
|
|
|
666
757
|
}
|
|
667
758
|
}
|
|
668
759
|
|
|
669
|
-
/**
|
|
760
|
+
/**
|
|
670
761
|
* Tasks which are run in the early phases of initialization
|
|
671
762
|
* - plumb in global.XMLHttpRequest which lives forever -- that way KeepAlive etc works.
|
|
672
763
|
*/
|
|
@@ -675,14 +766,14 @@ exports._initHead = function dcpClient$$initHead() {
|
|
|
675
766
|
|
|
676
767
|
if (typeof XMLHttpRequest === 'undefined')
|
|
677
768
|
XMLHttpRequest = require('dcp/dcp-xhr').XMLHttpRequest;
|
|
678
|
-
|
|
679
|
-
require('dcp/signal-handler').init();
|
|
769
|
+
|
|
770
|
+
require('dcp/signal-handler').init(); /* plumb in dcpExit event support for dcp-client applications */
|
|
680
771
|
}
|
|
681
772
|
|
|
682
|
-
/**
|
|
773
|
+
/**
|
|
683
774
|
* Tasks which are run in the late phases of initialization:
|
|
684
|
-
* 1 - activate either the local bundle or the remote bundle
|
|
685
|
-
* using the latest config (future: bootstrap bundle will export fewer modules until init;
|
|
775
|
+
* 1 - activate either the local bundle or the remote bundle in a fresh funtion scope
|
|
776
|
+
* using the latest config (future: bootstrap bundle will export fewer modules until init;
|
|
686
777
|
* bundle will provide post-initialization nsMap).
|
|
687
778
|
* 2 - inject modules from the final bundle on top of the bootstrap modules
|
|
688
779
|
* 3 - patch up internal (to the final bundle) references to dcpConfig to reference our generated config
|
|
@@ -711,19 +802,21 @@ function initTail(configFrags, options, finalBundleCode, finalBundleURL)
|
|
|
711
802
|
var schedConfLocFun = require('dcp/protocol').getSchedulerConfigLocation;
|
|
712
803
|
|
|
713
804
|
/* 1 */
|
|
714
|
-
|
|
805
|
+
bundleScope.dcpConfig = configFrags.internalConfig;
|
|
715
806
|
if (finalBundleCode) {
|
|
716
807
|
finalBundleLabel = String(finalBundleURL);
|
|
717
|
-
bundle
|
|
808
|
+
debug('dcp-client:bundle')(' - loading final bundle from web');
|
|
809
|
+
bundle = evalBundleCodeInIIFE(finalBundleCode, bundleScope, { filename: finalBundleLabel });
|
|
718
810
|
} else {
|
|
719
811
|
const bundleFilename = path.resolve(distDir, 'dcp-client-bundle.js');
|
|
720
812
|
finalBundleLabel = bundleFilename;
|
|
721
|
-
bundle
|
|
813
|
+
debug('dcp-client:bundle')(' - loading final bundle from disk');
|
|
814
|
+
bundle = evalBundleFileInIIFE(bundleFilename, bundleScope);
|
|
722
815
|
}
|
|
723
816
|
nsMap = bundle.nsMap || require('./ns-map'); /* future: need to move non-bootstrap nsMap into bundle for stable auto-update */
|
|
724
817
|
|
|
725
|
-
if (bundle.initTailHook) /* for use by auto-update future backwards compat */
|
|
726
|
-
bundle.initTailHook(configFrags, bundle, finalBundleLabel,
|
|
818
|
+
if (bundle.initTailHook) /* for use by auto-update future backwards compat */
|
|
819
|
+
bundle.initTailHook(configFrags, bundle, finalBundleLabel, bundleScope, injectModule);
|
|
727
820
|
|
|
728
821
|
/* 2 */
|
|
729
822
|
debug('dcp-client:modules')(`Begin phase 2 module injection '${finalBundleLabel}'`);
|
|
@@ -746,10 +839,11 @@ function initTail(configFrags, options, finalBundleCode, finalBundleURL)
|
|
|
746
839
|
* dcpUrl patchup utility and thus a full traversal of the dcpConfig object graph.
|
|
747
840
|
*/
|
|
748
841
|
const patchupList = [
|
|
749
|
-
{ how: 'kvin', wrong:
|
|
750
|
-
{ how: 'kvin', wrong:
|
|
751
|
-
{ how: 'ctor', wrong: URL,
|
|
842
|
+
{ how: 'kvin', wrong: bootstrapClasses.Address, right: require('dcp/wallet').Address },
|
|
843
|
+
{ how: 'kvin', wrong: bootstrapClasses.DcpURL, right: require('dcp/dcp-url').DcpURL },
|
|
844
|
+
{ how: 'ctor', wrong: URL, right: require('dcp/dcp-url').DcpURL },
|
|
752
845
|
];
|
|
846
|
+
assert(require('dcp/dcp-url').DcpURL !== bootstrapClasses.DcpURL);
|
|
753
847
|
patchupClasses(patchupList, configFrags);
|
|
754
848
|
|
|
755
849
|
/* Ensure KVIN deserialization from now on uses the current bundle's implementation for these classes */
|
|
@@ -773,9 +867,9 @@ function initTail(configFrags, options, finalBundleCode, finalBundleURL)
|
|
|
773
867
|
addConfig(workingDcpConfig, configFrags.localConfig);
|
|
774
868
|
addConfig(workingDcpConfig, originalDcpConfig);
|
|
775
869
|
|
|
776
|
-
|
|
870
|
+
bundleScope.dcpConfig = workingDcpConfig;
|
|
777
871
|
globalThis.dcpConfig = workingDcpConfig;
|
|
778
|
-
|
|
872
|
+
bundleScope.dcpConfig.build = require('dcp/build').config.build; /* dcpConfig.build deprecated mar 2023 /wg */
|
|
779
873
|
|
|
780
874
|
/* 4 */
|
|
781
875
|
if (workingDcpConfig.scheduler.configLocation !== false && typeof process.env.DCP_CLIENT_SKIP_VERSION_CHECK === 'undefined')
|
|
@@ -793,33 +887,20 @@ function initTail(configFrags, options, finalBundleCode, finalBundleURL)
|
|
|
793
887
|
throw require('dcp/utils').versionError('DCP Protocol', 'dcp-client', workingDcpConfig.scheduler.location.href, minVer, 'EDCP_PROTOCOL_VERSION');
|
|
794
888
|
}
|
|
795
889
|
}
|
|
796
|
-
|
|
797
|
-
/* 5 */
|
|
798
|
-
if (options.parseArgv !== false) {
|
|
799
|
-
const dcpCli = require('dcp/cli');
|
|
800
|
-
/* don't enable help output when automating */
|
|
801
|
-
const argv = dcpCli.base().help(false).argv;
|
|
802
|
-
const { help, identity, identityFile, defaultBankAccount, defaultBankAccountFile } = argv;
|
|
803
|
-
|
|
804
|
-
if (!help) {
|
|
805
|
-
const wallet = require('dcp/wallet');
|
|
806
|
-
if (identity || identityFile) {
|
|
807
|
-
const idKs_p = dcpCli.getIdentityKeystore();
|
|
808
|
-
wallet.addId(idKs_p);
|
|
809
|
-
}
|
|
810
890
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
891
|
+
/* 5 */
|
|
892
|
+
if (options.parseArgv !== false)
|
|
893
|
+
{
|
|
894
|
+
let optarg;
|
|
895
|
+
if ((optarg = consumeArg('identity')))
|
|
896
|
+
require('dcp/identity').setDefault(optarg);
|
|
816
897
|
}
|
|
817
898
|
|
|
818
899
|
/* 6 */
|
|
819
900
|
ret = makeInitReturnObject();
|
|
820
|
-
if (bundle.postInitTailHook) /* for use by auto-update future backwards compat */
|
|
821
|
-
ret = bundle.postInitTailHook(ret, configFrags, bundle, finalBundleLabel,
|
|
822
|
-
dcpConfig.build =
|
|
901
|
+
if (bundle.postInitTailHook) /* for use by auto-update future backwards compat */
|
|
902
|
+
ret = bundle.postInitTailHook(ret, configFrags, bundle, finalBundleLabel, bundleScope, injectModule);
|
|
903
|
+
dcpConfig.build = bundleScope.dcpConfig.build = require('dcp/build').config.build; /* dcpConfig.build deprecated March 2023 */
|
|
823
904
|
|
|
824
905
|
return ret;
|
|
825
906
|
}
|
|
@@ -829,7 +910,7 @@ function initTail(configFrags, options, finalBundleCode, finalBundleURL)
|
|
|
829
910
|
* object with two properies:
|
|
830
911
|
* - localConfig: a dcpConfig fragment
|
|
831
912
|
* - options: an options object
|
|
832
|
-
*
|
|
913
|
+
*
|
|
833
914
|
* This routine also populates certain key default values, such as the scheduler and program name that
|
|
834
915
|
* need to always be defined, and updates the localConfig fragment to reflect appropriate options.
|
|
835
916
|
*
|
|
@@ -873,7 +954,7 @@ function handleInitArgs(initArgv)
|
|
|
873
954
|
|
|
874
955
|
options.dcpConfig = Object.assign(initConfig, options.dcpConfig);
|
|
875
956
|
if (options.scheduler)
|
|
876
|
-
initConfig.scheduler.location = new URL(options.scheduler);
|
|
957
|
+
initConfig.scheduler.location = new URL(options.scheduler);
|
|
877
958
|
if (options.autoUpdate)
|
|
878
959
|
initConfig.bundle.autoUpdate = true;
|
|
879
960
|
if (options.bundleLocation)
|
|
@@ -887,7 +968,7 @@ function handleInitArgs(initArgv)
|
|
|
887
968
|
|
|
888
969
|
/**
|
|
889
970
|
* Initialize the dcp-client bundle for use by the compute API, etc. - Form 1
|
|
890
|
-
*
|
|
971
|
+
*
|
|
891
972
|
* @param {string} url
|
|
892
973
|
* Location of scheduler, from whom we download
|
|
893
974
|
* dcp-config.js, which in turn tells us where to
|
|
@@ -896,7 +977,7 @@ function handleInitArgs(initArgv)
|
|
|
896
977
|
*/
|
|
897
978
|
/**
|
|
898
979
|
* Form 2
|
|
899
|
-
*
|
|
980
|
+
*
|
|
900
981
|
* @param {URL object} url
|
|
901
982
|
* Location of scheduler, from whom we download
|
|
902
983
|
* dcp-config.js, which in turn tells us where to
|
|
@@ -936,6 +1017,8 @@ exports.init = async function dcpClient$$init() {
|
|
|
936
1017
|
var finalBundleCode = false;
|
|
937
1018
|
var finalBundleURL;
|
|
938
1019
|
|
|
1020
|
+
debug('dcp-client:init')(' * Initializing dcp-client');
|
|
1021
|
+
|
|
939
1022
|
reportErrors = options.reportErrors;
|
|
940
1023
|
exports._initHead();
|
|
941
1024
|
configFrags = await exports.createConfigFragments(initConfig, options);
|
|
@@ -957,7 +1040,10 @@ exports.init = async function dcpClient$$init() {
|
|
|
957
1040
|
}
|
|
958
1041
|
|
|
959
1042
|
if (process.env.DCP_CLIENT_ENABLE_SOURCEMAPS || options.enableSourceMaps)
|
|
1043
|
+
{
|
|
1044
|
+
debug('dcp-client:init')(' * Installing source maps');
|
|
960
1045
|
require('source-map-support').install();
|
|
1046
|
+
}
|
|
961
1047
|
|
|
962
1048
|
return initTail(configFrags, options, finalBundleCode, finalBundleURL);
|
|
963
1049
|
}
|
|
@@ -970,7 +1056,7 @@ exports.initSync = function dcpClient$$initSync() {
|
|
|
970
1056
|
var configFrags;
|
|
971
1057
|
var finalBundleCode = false;
|
|
972
1058
|
var finalBundleURL;
|
|
973
|
-
|
|
1059
|
+
|
|
974
1060
|
exports._initHead();
|
|
975
1061
|
configFrags = createConfigFragmentsSync(initConfig, options);
|
|
976
1062
|
|
|
@@ -1015,14 +1101,26 @@ function mkEnvConfig()
|
|
|
1015
1101
|
|
|
1016
1102
|
/**
|
|
1017
1103
|
* Generate a local config object from the program's command line
|
|
1104
|
+
* Side effect: process.argv is modified to remove these options
|
|
1018
1105
|
*/
|
|
1019
|
-
function mkCliConfig(
|
|
1106
|
+
function mkCliConfig(options)
|
|
1020
1107
|
{
|
|
1021
|
-
const cliConfig = { scheduler: {} };
|
|
1108
|
+
const cliConfig = mkCliConfig.__cache || { scheduler: {}, bundle: {} };
|
|
1109
|
+
mkCliConfig.__cache = cliConfig;
|
|
1022
1110
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1111
|
+
if (options.parseArgv !== false)
|
|
1112
|
+
{
|
|
1113
|
+
let s;
|
|
1114
|
+
debug('dcp-client:config')(` * Loading configuration from command-line arguments`);
|
|
1115
|
+
if ((s = consumeArg('scheduler')))
|
|
1116
|
+
cliConfig.scheduler.location = new URL(s);
|
|
1117
|
+
if ((s = consumeArg('config-location')))
|
|
1118
|
+
cliConfig.scheduler.configLocation = s;
|
|
1119
|
+
if ((s = consumeArg('bundle-location')))
|
|
1120
|
+
cliConfig.scheduler.configLocation = s;
|
|
1121
|
+
if ((consumeArg('bundle-auto-update', true)))
|
|
1122
|
+
cliConfig.scheduler.configLocation = true;
|
|
1123
|
+
}
|
|
1026
1124
|
|
|
1027
1125
|
return cliConfig;
|
|
1028
1126
|
}
|
|
@@ -1044,8 +1142,7 @@ function mkCliConfig(cliOpts)
|
|
|
1044
1142
|
* - scheduler {string|URL}: location of the DCP scheduler
|
|
1045
1143
|
* - autoUpdate {boolean}: true to download a fresh dcp-client bundle from the scheduler
|
|
1046
1144
|
* - bundleLocation {string|URL}: location from where we will download a new bundle
|
|
1047
|
-
* -
|
|
1048
|
-
* - configName {string}: arbitrary name to use to resolve a config fragment filename
|
|
1145
|
+
* - configName {string}: arbitrary name to use to resolve a config fragment filename
|
|
1049
1146
|
* relative to the program module
|
|
1050
1147
|
* @returns {object} with the following properties:
|
|
1051
1148
|
* - defaultConfig: this is the configuration buried in dcp-client (mostly from the bundle but also index.js)
|
|
@@ -1055,9 +1152,9 @@ function mkCliConfig(cliOpts)
|
|
|
1055
1152
|
*/
|
|
1056
1153
|
exports.createConfigFragments = async function dcpClient$$createConfigFragments(initConfig, options)
|
|
1057
1154
|
{
|
|
1058
|
-
/* The steps that are followed are in a very careful order; there are default configuration options
|
|
1059
|
-
* which can be overridden by either the API consumer or the scheduler; it is important that the wishes
|
|
1060
|
-
* of the API consumer always take priority, and that the scheduler is unable to override parts of
|
|
1155
|
+
/* The steps that are followed are in a very careful order; there are default configuration options
|
|
1156
|
+
* which can be overridden by either the API consumer or the scheduler; it is important that the wishes
|
|
1157
|
+
* of the API consumer always take priority, and that the scheduler is unable to override parts of
|
|
1061
1158
|
* dcpConfig which are security-critical, like allowOrigins, minimum wage, bundle auto update, etc.
|
|
1062
1159
|
*
|
|
1063
1160
|
* 1 - create the local config by
|
|
@@ -1069,17 +1166,15 @@ exports.createConfigFragments = async function dcpClient$$createConfigFragments(
|
|
|
1069
1166
|
* - arguments to init()
|
|
1070
1167
|
* - use the config + environment + arguments to figure out where the scheduler is
|
|
1071
1168
|
* - etc (see dcp-docs file dcp-config-file-regkey-priorities)
|
|
1072
|
-
* 2 - merge the passed-in configuration on top of the default configuration
|
|
1169
|
+
* 2 - merge the passed-in configuration on top of the default configuration
|
|
1073
1170
|
* 5 - pull the scheduler's config, and layer it on top of the current configuration to find scheduler
|
|
1074
1171
|
*/
|
|
1075
|
-
const cliOpts = require('dcp/cli').base().help(false).parse();
|
|
1076
1172
|
const etc = process.env.DCP_ETCDIR || (os.platform() === 'win32' ? process.env.ALLUSERSPROFILE : '/etc');
|
|
1077
1173
|
const home = process.env.DCP_HOMEDIR || os.homedir();
|
|
1078
1174
|
let programName = options.programName;
|
|
1079
|
-
const configScope = cliOpts.configScope || process.env.DCP_CONFIG_SCOPE || options.configScope;
|
|
1080
1175
|
const progDir = process.mainModule ? path.dirname(process.mainModule.filename) : process.cwd();
|
|
1081
1176
|
var remoteConfig, remoteConfigKVIN;
|
|
1082
|
-
const internalConfig = require('dcp/dcp-config');
|
|
1177
|
+
const internalConfig = require('dcp/dcp-config');
|
|
1083
1178
|
const defaultConfig = Object.assign({}, bootstrapConfig);
|
|
1084
1179
|
addConfig(defaultConfig, internalConfig);
|
|
1085
1180
|
addConfig(defaultConfig, KVIN.unmarshal(require('dcp/internal/dcp-default-config')));
|
|
@@ -1091,7 +1186,7 @@ exports.createConfigFragments = async function dcpClient$$createConfigFragments(
|
|
|
1091
1186
|
* then these leaf nodes eventually overwrite the same-pathed nodes which arrive in the remote conf.
|
|
1092
1187
|
*
|
|
1093
1188
|
* The pre-populated localConfig graph then has each of its nodes inherit from the equivalent node
|
|
1094
|
-
* in defaultConfig. This allows us to read properties in localConfig and get values from
|
|
1189
|
+
* in defaultConfig. This allows us to read properties in localConfig and get values from
|
|
1095
1190
|
* defaultConfig (assuming they haven't been overwritten), but writing to localConfig won't alter
|
|
1096
1191
|
* the defaultConfig. This is important, because need to preserve the config stack but allow
|
|
1097
1192
|
* user-supplied dcp-config.js files to read the existing config and use it to generate new
|
|
@@ -1107,7 +1202,7 @@ exports.createConfigFragments = async function dcpClient$$createConfigFragments(
|
|
|
1107
1202
|
|
|
1108
1203
|
/* "warm up" the local ahead of actually reading in the config files. These options are higher-
|
|
1109
1204
|
* precedence than reading them at the base level, but providing them at the base level first
|
|
1110
|
-
* means that lower-level config files can see them. The
|
|
1205
|
+
* means that lower-level config files can see them. The classic example again is that the worker
|
|
1111
1206
|
* needs to know the scheduler location in order to generate the default allow lists, but the
|
|
1112
1207
|
* scheduler location can be override by the environment or command-line. The one thing that
|
|
1113
1208
|
* we can't really support easily is having a config file modify something in terms of what
|
|
@@ -1117,7 +1212,7 @@ exports.createConfigFragments = async function dcpClient$$createConfigFragments(
|
|
|
1117
1212
|
addConfig (localConfig, initConfig);
|
|
1118
1213
|
addConfigEnv (localConfig, 'DCP_CONFIG_');
|
|
1119
1214
|
addConfig (localConfig, mkEnvConfig());
|
|
1120
|
-
addConfig (localConfig, mkCliConfig(
|
|
1215
|
+
addConfig (localConfig, mkCliConfig(options));
|
|
1121
1216
|
|
|
1122
1217
|
/**
|
|
1123
1218
|
* 4. Use the config + environment + arguments to figure out where the
|
|
@@ -1125,9 +1220,9 @@ exports.createConfigFragments = async function dcpClient$$createConfigFragments(
|
|
|
1125
1220
|
*
|
|
1126
1221
|
* Only override the scheduler from argv if cli specifies a scheduler.
|
|
1127
1222
|
* e.g. the user specifies a --dcp-scheduler option.
|
|
1128
|
-
* See spec doc dcp-config-file-regkey-priorities
|
|
1223
|
+
* See spec doc dcp-config-file-regkey-priorities
|
|
1129
1224
|
* Note: this code is Sep 2022, overriding older spec, spec update to come. /wg
|
|
1130
|
-
*
|
|
1225
|
+
*
|
|
1131
1226
|
* The basic idea is that key collisions are overridden on the basis of "most specificity to current
|
|
1132
1227
|
* executable wins" - so local disk is stronger than remote network config, homedir is stronger than
|
|
1133
1228
|
* /etc, BUT we also have an override in /etc, and specific-program-name config files in /etc are more
|
|
@@ -1144,22 +1239,18 @@ exports.createConfigFragments = async function dcpClient$$createConfigFragments(
|
|
|
1144
1239
|
await addConfigRKey(localConfig, 'HKCU', 'dcp/dcp-config');
|
|
1145
1240
|
addConfigFile(localConfig, home, `.dcp/${programName}/dcp-config`);
|
|
1146
1241
|
await addConfigRKey(localConfig, 'HKCU', `dcp/${programName}/dcp-config`);
|
|
1147
|
-
addConfigFile(localConfig, home, '.dcp/scope', configScope);
|
|
1148
|
-
await addConfigRKey(localConfig, 'HKCU', 'dcp/scope', configScope);
|
|
1149
1242
|
addConfig (localConfig, initConfig);
|
|
1150
1243
|
addConfigEnv (localConfig, 'DCP_CONFIG_');
|
|
1151
1244
|
addConfig (localConfig, mkEnvConfig());
|
|
1152
|
-
addConfig (localConfig, mkCliConfig(
|
|
1245
|
+
addConfig (localConfig, mkCliConfig(options));
|
|
1153
1246
|
addConfigFile(localConfig, etc, `dcp/${programName}/dcp-config`);
|
|
1154
1247
|
await addConfigRKey(localConfig, 'HKLM', `dcp/${programName}/dcp-config`);
|
|
1155
1248
|
addConfigFile(localConfig, etc, 'dcp/override-dcp-config');
|
|
1156
1249
|
await addConfigRKey(localConfig, 'HKLM', 'dcp/override-dcp-config');
|
|
1157
|
-
addConfigFile(localConfig, etc, 'dcp/scope', configScope);
|
|
1158
|
-
await addConfigRKey(localConfig, 'HKLM', 'dcp/scope', configScope);
|
|
1159
1250
|
await addConfigRKey(localConfig, 'HKLM', 'dcp-client/dcp-config'); /* legacy - used by screen saver, /wg sep'22 */
|
|
1160
1251
|
|
|
1161
1252
|
exports.__cn = cn; /* memoize for use by dcp-worker etc who need to know where local conf came from */
|
|
1162
|
-
|
|
1253
|
+
|
|
1163
1254
|
/* 5. Use the aggregate of the default and local configs to figure out where the scheduler is. Use
|
|
1164
1255
|
* this to figure where the web config is and where an auto-update bundle would be if auto-update
|
|
1165
1256
|
* were enabled.
|
|
@@ -1175,7 +1266,7 @@ exports.createConfigFragments = async function dcpClient$$createConfigFragments(
|
|
|
1175
1266
|
if (!aggrConfig.bundle.location && aggrConfig.bundle.location !== false)
|
|
1176
1267
|
aggrConfig.bundle.location = localConfig.bundle.location = aggrConfig.scheduler.location.resolveUrl('/dcp-client/dist/dcp-client-bundle.js')
|
|
1177
1268
|
localConfig.scheduler.location = aggrConfig.scheduler.location;
|
|
1178
|
-
|
|
1269
|
+
|
|
1179
1270
|
debug('dcp-client:config')(` . scheduler is at ${localConfig.scheduler.location}`);
|
|
1180
1271
|
debug('dcp-client:config')(` . auto-update is ${localConfig.bundle.autoUpdate ? 'on' : 'off'}; bundle is at ${localConfig.bundle.location}`);
|
|
1181
1272
|
|
|
@@ -1202,7 +1293,7 @@ exports.createConfigFragments = async function dcpClient$$createConfigFragments(
|
|
|
1202
1293
|
throw error;
|
|
1203
1294
|
}
|
|
1204
1295
|
}
|
|
1205
|
-
|
|
1296
|
+
|
|
1206
1297
|
/**
|
|
1207
1298
|
* Default location for the auto update bundle is the scheduler so that the
|
|
1208
1299
|
* scheduler and the client are on the same code.
|
|
@@ -1217,9 +1308,9 @@ exports.createConfigFragments = async function dcpClient$$createConfigFragments(
|
|
|
1217
1308
|
|
|
1218
1309
|
exports.initcb = require('./init-common').initcb
|
|
1219
1310
|
|
|
1220
|
-
/**
|
|
1221
|
-
* Create the aggregate config - which by definition does async work - by
|
|
1222
|
-
* spawning another process (and other reactor) and then blocking until
|
|
1311
|
+
/**
|
|
1312
|
+
* Create the aggregate config - which by definition does async work - by
|
|
1313
|
+
* spawning another process (and other reactor) and then blocking until
|
|
1223
1314
|
* it is available. Used to implement initSync().
|
|
1224
1315
|
*
|
|
1225
1316
|
* The other process receives the same command-line options and environment
|
|
@@ -1254,7 +1345,7 @@ function createConfigFragmentsSync(initConfig, options)
|
|
|
1254
1345
|
input
|
|
1255
1346
|
},
|
|
1256
1347
|
);
|
|
1257
|
-
|
|
1348
|
+
|
|
1258
1349
|
if (child.status !== 0 || !child.output[3] || child.output[3].length === 0)
|
|
1259
1350
|
throw new Error(`Error running ${spawnArgv[0]} (exitCode=${child.status})`);
|
|
1260
1351
|
|
|
@@ -1265,7 +1356,7 @@ function createConfigFragmentsSync(initConfig, options)
|
|
|
1265
1356
|
debug('dcp-client:init')('fetched configuration fragments', Object.keys(configFrags));
|
|
1266
1357
|
return configFrags;
|
|
1267
1358
|
}
|
|
1268
|
-
|
|
1359
|
+
|
|
1269
1360
|
/** Fetch a web resource from the server, blocking the event loop. Used by initSync() to
|
|
1270
1361
|
* fetch the remote scheduler's configuration and optionally its autoUpdate bundle. The
|
|
1271
1362
|
* download program displays HTTP-level errors on stderr, so we simply inhert and let it
|
|
@@ -1280,21 +1371,21 @@ exports.fetchSync = function fetchSync(url) {
|
|
|
1280
1371
|
var child;
|
|
1281
1372
|
var argv = [ process.execPath, require.resolve('./bin/download'), '--fd=3' ];
|
|
1282
1373
|
var env = { FORCE_COLOR: 1 };
|
|
1283
|
-
|
|
1374
|
+
|
|
1284
1375
|
if (reportErrors === false)
|
|
1285
1376
|
argv.push('--silent');
|
|
1286
1377
|
if (typeof url !== 'string')
|
|
1287
1378
|
url = url.href;
|
|
1288
1379
|
argv.push(url);
|
|
1289
1380
|
|
|
1290
|
-
child = spawnSync(argv[0], argv.slice(1), {
|
|
1381
|
+
child = spawnSync(argv[0], argv.slice(1), {
|
|
1291
1382
|
env: Object.assign(env, process.env), shell: false, windowsHide: true,
|
|
1292
1383
|
stdio: [ 'ignore', 'inherit', 'inherit', 'pipe' ],
|
|
1293
1384
|
|
|
1294
1385
|
/**
|
|
1295
1386
|
* Setting the largest amount of data in bytes allowed on stdout or stderr to 10 MB
|
|
1296
1387
|
* so that dcp-client-bundle.js (~5.2 MB built in debug mode with source-mapped line
|
|
1297
|
-
* in late jan 2023) can be downloaded without the child exiting with a status of
|
|
1388
|
+
* in late jan 2023) can be downloaded without the child exiting with a status of
|
|
1298
1389
|
* null (i.e. ENOBUFS).
|
|
1299
1390
|
*/
|
|
1300
1391
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -1314,7 +1405,7 @@ exports.__require = require;
|
|
|
1314
1405
|
*
|
|
1315
1406
|
* This is the return value from initSync() and the promise resolution value for init().
|
|
1316
1407
|
*/
|
|
1317
|
-
function makeInitReturnObject() {
|
|
1408
|
+
function makeInitReturnObject() {
|
|
1318
1409
|
const nsMap = require('./ns-map');
|
|
1319
1410
|
var o = {};
|
|
1320
1411
|
|
|
@@ -1327,3 +1418,47 @@ function makeInitReturnObject() {
|
|
|
1327
1418
|
|
|
1328
1419
|
return o;
|
|
1329
1420
|
}
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* Consume an argument from process.argv, modifying process.argv so as to hide this argument's existence
|
|
1424
|
+
* from the dcp-client app consumer.
|
|
1425
|
+
*/
|
|
1426
|
+
function consumeArg(ckArg, bool)
|
|
1427
|
+
{
|
|
1428
|
+
var ret;
|
|
1429
|
+
ckArg = '--dcp-' + ckArg;
|
|
1430
|
+
|
|
1431
|
+
for (let opt, i=2; i < process.argv.length; i++)
|
|
1432
|
+
{
|
|
1433
|
+
const arg = process.argv[i];
|
|
1434
|
+
|
|
1435
|
+
if (bool ? arg === ckArg : arg.startsWith(ckArg + '='))
|
|
1436
|
+
{
|
|
1437
|
+
debug('dcp-client:config-argv')('consume', process.argv[i]);
|
|
1438
|
+
process.argv.splice(i, 1);
|
|
1439
|
+
if (!bool)
|
|
1440
|
+
ret = arg.slice(2 + ckArg.length + 1);
|
|
1441
|
+
else
|
|
1442
|
+
{
|
|
1443
|
+
/* when ckArg = opt, we can have
|
|
1444
|
+
* --dcp-opt => true
|
|
1445
|
+
* --dcp-opt=true => true
|
|
1446
|
+
* --dcp-opt=false => false
|
|
1447
|
+
*/
|
|
1448
|
+
if (arg === ckArg)
|
|
1449
|
+
ret = true;
|
|
1450
|
+
else
|
|
1451
|
+
{
|
|
1452
|
+
const tf = arg.slice(ckArg.length + 1);
|
|
1453
|
+
if (tf !== 'true' && tf !== 'false')
|
|
1454
|
+
throw new Error(`invalid argument ${tf} for ${ckArg}; should be true or false`);
|
|
1455
|
+
ret = tf === 'true';
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
ret = bool ? true : arg.slice(ckArg.length + 1);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
return ret;
|
|
1464
|
+
}
|