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/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 || false; /* not false if user set their own dcpConfig global variable before init */
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
- build: 'bootstrap',
53
- bundleConfig: true,
54
- scheduler: { location: new URL('http://bootstrap.distributed.computer/') },
55
- bank: { location: new URL('http://bootstrap.distributed.computer/') },
56
- packageManager: { location: new URL('http://bootstrap.distributed.computer/') },
57
- needs: { urlPatchUp: true },
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 bundleSandbox = {
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
- function runSandboxedCode(sandbox, code, options)
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 ctx = vm.createContext(sandbox);
96
- const script = new vm.Script(code, options);
97
- return script.runInContext(ctx, options);
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
- /** Evaluate a file in a sandbox without polluting the global object.
101
- * @param filename {string} The name of the file to evaluate, relative to
102
- * @param sandbox {object} A sandbox object, used for injecting 'global' symbols as needed
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 evalScriptInSandbox(filename, sandbox)
128
+ function evalBundleCodeInIIFE(code, gsymbox, options)
105
129
  {
106
- var code
107
- try {
108
- debug('dcp-client:evalScriptInSandbox')('evaluating', filename);
109
- code = fs.readFileSync(path.resolve(distDir, filename), 'utf8');
110
- } catch(e) {
111
- if (e.code === 'ENOENT')
112
- return {}
113
- debug('dcp-client:evalScriptInSandbox')(e);
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
- return runSandboxedCode(sandbox, code, { filename, lineOffset: 0 });
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} sandbox An object which simulates the global object via symbol collision; any
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 evalFileInIIFE(filename, sandbox)
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
- const fun = vm.runInThisContext(prologue + fileContents + epilogue, options);
139
-
140
- return fun.apply(null, Object.values(sandbox));
156
+ return evalBundleCodeInIIFE(fileContents, gsymbox, { filename });
141
157
  }
142
158
 
143
- /** Evaluate code in a secure sandbox; in this case, the code is the configuration
144
- * file, and the sandbox is a special container with limited objects that we setup
145
- * during config file processing.
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 code {string} The code to eval
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 evalStringInSandbox(code, sandbox, filename = '(dcp-client$$evalStringInSandbox)')
167
+ function evalConfigFile(symbols, filename)
154
168
  {
155
- var result;
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: 12, /* use strict */
174
+ columnOffset: 0,
161
175
  displayErrors: !codeHasVeryLongLine
162
176
  };
163
177
 
164
- /* We support two types of strings - one produces a value, the other returns a value. Use the JS
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
- const nodejsErrorMessage = /^Illegal return statement/;
175
- const bunErrorMessage = /^Return statements are only valid inside functions./;
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
- return result;
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, sandbox as the config files and client code. This code is
199
- * evaluated with the bootstrap config as dcpConfig so that static initialization of dcpConfig in
200
- * any of the bootstrap modules can mutate the bottom-most layer of the dcpConfig stack.
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
- let sandbox = {}
204
-
205
- Object.assign(sandbox, bundleSandbox)
206
- sandbox.globalThis = sandbox;
207
- sandbox.window = sandbox;
208
- sandbox.dcpConfig = bootstrapConfig;
209
-
210
- return evalScriptInSandbox(path.resolve(distDir, 'dcp-client-bundle.js'), sandbox)
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
- /** Merge a new configuration object on top of an existing one. The new object
297
- * is overlaid on the existing object, so that properties specified in the
298
- * existing object graph overwrite, but unspecified edges are left alone.
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
- * Instances of URL and dcpUrl::URL receive special treatment: if they are being
301
- * overwritten by a string, the string is used the argument to the constructor
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
- * @param {object} existing The top node of an object graph whose
309
- * edges may be replaced
310
- * @param {object} neo The top node of an object graph whose
311
- * edges describe the replacement
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
- function addConfig (existing, neo) {
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
- for (let prop in neo) {
319
- if (!neo.hasOwnProperty(prop))
320
- continue;
321
- if (typeof existing[prop] === 'object' && DcpURL.isURL(existing[prop])) {
322
- if (neo[prop])
323
- existing[prop] = new (existing[prop].constructor)(neo[prop]);
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
- if (typeof neo[prop] === 'object' && neo[prop] !== null && !Array.isArray(neo[prop]) && ['Function','Object'].includes(neo[prop].constructor.name)) {
327
- if (typeof existing[prop] === 'undefined') {
328
- existing[prop] = {}
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 makeConfigSandbox()
553
+ function makeConfigFileSymbols()
462
554
  {
463
- var configSandbox = Object.assign({}, bundleSandbox, {
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 (!configSandbox.hasOwnProperty(key))
473
- configSandbox[key] = configSandbox.dcpConfig[key];
564
+ if (!configFileScope.hasOwnProperty(key))
565
+ configFileScope[key] = configFileScope.dcpConfig[key];
474
566
 
475
- assert(configSandbox.console);
476
- return configSandbox;
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 URL from the
604
- * internal bundle and then download a new URL class, it won't be an instance of the new class and it
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
- o[key] = KVIN.unmarshal(KVIN.marshal(o[key]));
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 against a fresh sandbox
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
- bundleSandbox.dcpConfig = configFrags.internalConfig;
805
+ bundleScope.dcpConfig = configFrags.internalConfig;
715
806
  if (finalBundleCode) {
716
807
  finalBundleLabel = String(finalBundleURL);
717
- bundle = evalStringInSandbox(finalBundleCode, bundleSandbox, finalBundleLabel);
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 = evalFileInIIFE(bundleFilename, bundleSandbox);
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, bundleSandbox, injectModule);
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: KVIN.userCtors.dcpEth$$Address, right: require('dcp/wallet').Address },
750
- { how: 'kvin', wrong: KVIN.userCtors.dcpUrl$$DcpURL, right: require('dcp/dcp-url').DcpURL },
751
- { how: 'ctor', wrong: URL, right: require('dcp/dcp-url').DcpURL },
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
- bundleSandbox.dcpConfig = workingDcpConfig;
870
+ bundleScope.dcpConfig = workingDcpConfig;
777
871
  globalThis.dcpConfig = workingDcpConfig;
778
- bundleSandbox.dcpConfig.build = require('dcp/build').config.build; /* dcpConfig.build deprecated mar 2023 /wg */
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
- if (defaultBankAccount || defaultBankAccountFile) {
812
- const bankKs_p = dcpCli.getAccountKeystore();
813
- wallet.add(bankKs_p);
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, bundleSandbox, injectModule);
822
- dcpConfig.build = bundleSandbox.dcpConfig.build = require('dcp/build').config.build; /* dcpConfig.build deprecated March 2023 */
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(cliOpts)
1106
+ function mkCliConfig(options)
1020
1107
  {
1021
- const cliConfig = { scheduler: {} };
1108
+ const cliConfig = mkCliConfig.__cache || { scheduler: {}, bundle: {} };
1109
+ mkCliConfig.__cache = cliConfig;
1022
1110
 
1023
- debug('dcp-client:config')(` * Loading configuration from CLI options`);
1024
-
1025
- if (cliOpts.dcpScheduler) cliConfig.scheduler.location = new URL(cliOpts.dcpScheduler);
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
- * - configScope {string}: arbitrary name to use in forming various config fragment filenames
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'); /* needed to resolve dcpConfig.future - would like to eliminate this /wg */
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 class example again is that the worker
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(cliOpts));
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(cliOpts));
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
+ }