@arcticnotes/node-wsh 0.0.9 → 0.0.11

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/README.md CHANGED
@@ -18,10 +18,10 @@ In JavaScript code:
18
18
  ```javascript
19
19
  import {WindowsScriptingHost} from '@arcticnotes/node-wsh';
20
20
 
21
- const wsh = await WindowsScriptingHost.connect();
22
- const WScript = wsh.WScript;
21
+ const WSH = await WindowsScriptingHost.connect();
22
+ const WScript = WSH.global( 'WScript');
23
23
  console.log(WScript.Version);
24
- await wsh.disconnect();
24
+ await WSH.disconnect();
25
25
  ```
26
26
 
27
27
  ## Dependencies
@@ -3,10 +3,8 @@ import PATH from 'node:path';
3
3
  import { Syncline} from "@arcticnotes/syncline";
4
4
 
5
5
  const COMMAND = 'cscript.exe';
6
- const ARGS = [ '//E:jscript', '//NoLogo'];
7
- const SCRIPT_FILE = PATH.join( PATH.dirname( import.meta.dirname), 'wsh', 'host.js');
8
- const PROXY = Symbol();
9
- const TRACE_REF = 1;
6
+ const ARGS = [ '//NoLogo'];
7
+ const SCRIPT_FILE = PATH.join( PATH.dirname( import.meta.dirname), 'wsh', 'host.wsf');
10
8
 
11
9
  export class WindowsScriptingHost extends EventEmitter {
12
10
 
@@ -15,172 +13,119 @@ export class WindowsScriptingHost extends EventEmitter {
15
13
  const args = options.args || ARGS;
16
14
  const scriptFile = options.scriptFile || SCRIPT_FILE;
17
15
  const trace = options.trace || 0;
18
- return new WindowsScriptingHost( await Syncline.spawn( command, [ ...args, scriptFile], { trace}), options);
16
+ return new WindowsScriptingHost( await Syncline.spawn( command, [ ...args, scriptFile], { trace}));
19
17
  }
20
18
 
21
19
  #syncline;
22
- #proxies;
23
- #WScript;
24
- #GetObject;
25
- #Enumerator;
26
-
27
- constructor( syncline, options) {
28
- super();
29
- this.#syncline = syncline;
30
- this.#syncline.on( 'stderr', line => console.log( 'wsh:', line));
31
- this.#syncline.on( 'stdout', line => console.log( 'wsh:', line));
32
- this.#proxies = new Proxies( syncline, options, this);
33
- this.#WScript = this.#proxies.getOrCreateObject( 0);
34
- this.#GetObject = this.#proxies.getOrCreateFunction( 1);
35
- this.#Enumerator = this.#proxies.getOrCreateFunction( 2);
36
- }
37
-
38
- get remoteObjects() {
39
- const proxies = this.#proxies;
40
- return {
41
- get count() {
42
- return proxies.count;
43
- },
44
- };
45
- }
46
-
47
- get WScript() {
48
- return this.#WScript;
49
- }
50
-
51
- get GetObject() {
52
- return this.#GetObject;
53
- }
54
-
55
- get Enumerator() {
56
- return this.#Enumerator;
57
- }
58
-
59
- async disconnect() {
60
- await this.#syncline.close();
61
- }
62
- }
63
-
64
- class Proxies {
65
-
66
- #syncline;
67
- #trace;
68
- #eventEmitter;
20
+ #closed = false;
69
21
  #finalizer = new FinalizationRegistry( this.#finalized.bind( this));
70
22
  #ref2proxy = new Map();
71
- #proxy2ref = new Map();
72
-
73
- #objectHandler = {
23
+ #proxy2ref = new WeakMap();
24
+ #handler = {
74
25
 
75
- proxies: this,
26
+ wsh: this,
76
27
 
77
28
  get( target, prop) {
78
29
  if( prop === Symbol.toPrimitive)
79
- return () => `ref#${ this.proxies.#proxy2ref.get( target[ PROXY])}`;
80
- const encodedTarget = this.proxies.#encode( target[ PROXY]);
81
- const encodedProp = this.proxies.#encode( prop);
82
- const output = JSON.parse( this.proxies.#syncline.exchange( JSON.stringify( [ 'get', encodedTarget, encodedProp])));
30
+ return () => `ref#${ target.ref}`;
31
+ const encodedTarget = this.wsh.#encode( target.proxy);
32
+ const encodedProp = this.wsh.#encode( prop);
33
+ const output = JSON.parse( this.wsh.#syncline.exchange( JSON.stringify( [ 'get', encodedTarget, encodedProp])));
83
34
  switch( output[ 0]) {
84
- case 'value': return this.proxies.#decode( output[ 1]);
35
+ case 'value': return this.wsh.#decode( output[ 1]);
85
36
  case 'error': throw new Error( output[ 1]);
86
37
  default: throw new Error( `unknown status: ${ output[ 0]}`);
87
38
  }
88
39
  },
89
40
 
90
41
  set( target, prop, value) {
91
- const encodedTarget = this.proxies.#encode( target[ PROXY]);
92
- const encodedProp = this.proxies.#encode( prop);
93
- const encodedValue = this.proxies.#encode( value);
94
- const output = JSON.parse( this.proxies.#syncline.exchange( JSON.stringify( [ 'set', encodedTarget, encodedProp, encodedValue])));
42
+ const encodedTarget = this.wsh.#encode( target.proxy);
43
+ const encodedProp = this.wsh.#encode( prop);
44
+ const encodedValue = this.wsh.#encode( value);
45
+ const output = JSON.parse( this.wsh.#syncline.exchange( JSON.stringify( [ 'set', encodedTarget, encodedProp, encodedValue])));
95
46
  switch( output[ 0]) {
96
47
  case 'set': return;
97
48
  case 'error': throw new Error( output[ 1]);
98
49
  default: throw new Error( `unknown status: ${ output[ 0]}`);
99
50
  }
100
51
  },
101
- };
102
-
103
- #functionHandler = {
104
-
105
- proxies: this,
106
-
107
- get( target, prop) {
108
- if( prop === Symbol.toPrimitive)
109
- return () => `ref#${ this.proxies.#proxy2ref.get( target[ PROXY])}`;
110
- return undefined;
111
- },
112
52
 
113
- apply( target, thisArg, argumentList) {
114
- const encodedTarget = this.proxies.#encode( target[ PROXY]);
115
- const encodedThisArg = this.proxies.#encode( thisArg);
116
- const encodedArgumentList = this.proxies.#encode( argumentList);
117
- const output = JSON.parse( this.proxies.#syncline.exchange( JSON.stringify( [ 'apply', encodedTarget, encodedThisArg, encodedArgumentList])));
53
+ apply( target, thisArg, argumentsList) {
54
+ const encodedTarget = this.wsh.#encode( target.proxy);
55
+ const encodedThisArg = this.wsh.#encode( thisArg);
56
+ const encodedArgumentList = this.wsh.#encode( [ ...argumentsList]); // argumentsList may not be instanceof Array
57
+ const output = JSON.parse( this.wsh.#syncline.exchange( JSON.stringify( [ 'apply', encodedTarget, encodedThisArg, encodedArgumentList])));
118
58
  switch( output[ 0]) {
119
- case 'value': return this.proxies.#decode( output[ 1]);
59
+ case 'value': return this.wsh.#decode( output[ 1]);
120
60
  case 'error': throw new Error( output[ 1]);
121
61
  default: throw new Error( `unknown status: ${ output[ 0]}`);
122
62
  }
123
63
  },
124
64
 
125
- construct( target, argumentList) {
126
- const encodedTarget = this.proxies.#encode( target[ PROXY]);
127
- const encodedArgumentList = this.proxies.#encode( argumentList);
128
- const output = JSON.parse( this.proxies.#syncline.exchange( JSON.stringify( [ 'construct', encodedTarget, encodedArgumentList])));
65
+ construct( target, argumentsList) {
66
+ const encodedTarget = this.wsh.#encode( target.proxy);
67
+ const encodedArgumentList = this.wsh.#encode( [ ...argumentsList]); // argumentsList may not be instanceof Array
68
+ const output = JSON.parse( this.wsh.#syncline.exchange( JSON.stringify( [ 'construct', encodedTarget, encodedArgumentList])));
129
69
  switch( output[ 0]) {
130
- case 'value': return this.proxies.#decode( output[ 1]);
70
+ case 'value': return this.wsh.#decode( output[ 1]);
131
71
  case 'error': throw new Error( output[ 1]);
132
72
  default: throw new Error( `unknown status: ${ output[ 0]}`);
133
73
  }
134
74
  },
135
75
  };
136
76
 
137
- constructor( syncline, options, eventEmitter) {
77
+ constructor( syncline) {
78
+ super();
138
79
  this.#syncline = syncline;
139
- this.#trace = options.trace;
140
- this.#eventEmitter = eventEmitter;
80
+ this.#syncline.on( 'stderr', line => console.log( 'wsh:', line));
81
+ this.#syncline.on( 'stdout', line => console.log( 'wsh:', line));
141
82
  }
142
83
 
143
- get count() {
144
- return this.#ref2proxy.size;
84
+ get remoteObjects() {
85
+ const proxies = this;
86
+ return {
87
+ get count() {
88
+ return proxies.#ref2proxy.size;
89
+ },
90
+ };
145
91
  }
146
92
 
147
- getOrCreateObject( ref) {
148
- return this.#getOrCreate( ref, this.#objectHandler);
93
+ global( name) {
94
+ const output = JSON.parse( this.#syncline.exchange( JSON.stringify( [ 'global', name])));
95
+ switch( output[ 0]) {
96
+ case 'value': return this.#decode( output[ 1]);
97
+ case 'error': throw new Error( output[ 1]);
98
+ default: throw new Error( `unknown status: ${ output[ 0]}`);
99
+ }
149
100
  }
150
101
 
151
- getOrCreateFunction( ref) {
152
- return this.#getOrCreate( ref, this.#functionHandler);
102
+ async disconnect() {
103
+ this.#closed = true;
104
+ await this.#syncline.close();
153
105
  }
154
106
 
155
- #getOrCreate( ref, handler) {
156
- const existingProxy = this.#ref2proxy.get( ref);
107
+ #getOrCreate( ref) {
108
+ const existingWeakRef = this.#ref2proxy.get( ref);
109
+ const existingProxy = existingWeakRef && existingWeakRef.deref();
157
110
  if( existingProxy)
158
111
  return existingProxy;
159
112
 
160
- const target = handler === this.#objectHandler? new RemoteObject(): function() {};
161
- const newProxy = new Proxy( target, handler);
162
- target[ PROXY] = newProxy;
163
- this.#ref2proxy.set( ref, newProxy);
113
+ const target = new RemoteObject( ref);
114
+ const newProxy = new Proxy( target, this.#handler);
115
+ target.proxy = newProxy;
116
+ this.#ref2proxy.set( ref, new WeakRef( newProxy)); // may be overwriting a dead WeakRef
164
117
  this.#proxy2ref.set( newProxy, ref);
165
118
  this.#finalizer.register( newProxy, ref);
166
- this.#eventEmitter.emit( 'ref', ref, newProxy);
119
+ this.emit( 'ref', ref, newProxy);
167
120
  return newProxy;
168
121
  }
169
122
 
170
123
  #finalized( ref) {
171
- const output = this.#syncline.exchange( JSON.stringify( [ 'unref', ref]));
172
- if( this.#trace >= TRACE_REF)
173
- switch( output[ 0]) {
174
- case 'error':
175
- console.log( `failed to unref: ${ ref}`);
176
- break;
177
- case 'done':
178
- console.log( `unreferenced: ${ ref}`);
179
- break;
180
- default:
181
- console.log( `unknown response: ${ output[ 0]}`);
182
- }
183
- this.#eventEmitter.emit( 'unref', ref);
124
+ if( this.#ref2proxy.get( ref).deref() === undefined) // otherwise, it's overwritten by a refreshed proxy
125
+ this.#ref2proxy.delete( ref);
126
+ if( !this.#closed)
127
+ this.#syncline.exchange( JSON.stringify( [ 'unref', ref]));
128
+ this.emit( 'unref', ref);
184
129
  }
185
130
 
186
131
  #encode( decoded) {
@@ -194,27 +139,24 @@ class Proxies {
194
139
  case 'object':
195
140
  if( decoded === null)
196
141
  return decoded;
197
- if( decoded instanceof Array) {
142
+ if( Array.isArray( decoded)) {
198
143
  const encoded = [];
199
144
  for( const item of decoded)
200
145
  encoded.push( this.#encode( item));
201
146
  return encoded;
202
147
  }
203
- if( decoded instanceof RemoteObject) {
204
- const objref = this.#proxy2ref.get( decoded);
205
- if( objref === undefined)
206
- throw new Error( `remote object reference not found: ${ decoded}`);
207
- return { type: 'objref', value: objref};
208
- }
209
148
  const encoded = { type: 'object', value: {}};
210
149
  for( const [ name, value] of Object.entries( decoded))
211
150
  encoded.value[ name] = this.#encode( value);
212
151
  return encoded;
213
152
  case 'function':
214
- const funref = this.#proxy2ref.get( decoded);
215
- if( funref === undefined)
216
- throw new Error( `functions from node are disallowed: ${ decoded}`);
217
- return { type: 'funref', value: funref};
153
+ if( decoded instanceof RemoteObject) {
154
+ const ref = this.#proxy2ref.get( decoded);
155
+ if( ref === undefined) // not because garbage-collected, because clearly `decoded` is still alive
156
+ throw new Error( `remote object reference not found: ${ decoded}`);
157
+ return { type: 'ref', value: ref};
158
+ }
159
+ throw new Error( `functions from node cannot be sent: ${ decoded}`);
218
160
  case 'bigint':
219
161
  case 'symbol':
220
162
  default:
@@ -231,7 +173,7 @@ class Proxies {
231
173
  case 'object':
232
174
  if( encoded === null)
233
175
  return encoded;
234
- if( encoded instanceof Array) {
176
+ if( Array.isArray( encoded)) {
235
177
  const decoded = [];
236
178
  for( const item of encoded)
237
179
  decoded.push( this.#decode( item));
@@ -245,10 +187,8 @@ class Proxies {
245
187
  for( const [ name, value] of Object.entries( encoded.value))
246
188
  decoded[ name] = this.#decode( value);
247
189
  return decoded;
248
- case 'objref':
249
- return this.getOrCreateObject( encoded.value);
250
- case 'funref':
251
- return this.getOrCreateFunction( encoded.value);
190
+ case 'ref':
191
+ return this.#getOrCreate( encoded.value);
252
192
  default:
253
193
  throw new Error( `illegal value: ${ encoded}`);
254
194
  }
@@ -262,5 +202,25 @@ class Proxies {
262
202
  }
263
203
  }
264
204
 
265
- class RemoteObject {
205
+ class RemoteObject extends Function {
206
+
207
+ #ref;
208
+ #proxy;
209
+
210
+ constructor( ref) {
211
+ super();
212
+ this.#ref = ref;
213
+ }
214
+
215
+ get ref() {
216
+ return this.#ref;
217
+ }
218
+
219
+ get proxy() {
220
+ return this.#proxy;
221
+ }
222
+
223
+ set proxy( proxy) {
224
+ this.#proxy = proxy;
225
+ }
266
226
  }
package/lib/wsh/host.js CHANGED
@@ -1,13 +1,18 @@
1
1
  // This file is in JScript, not in JavaScript. It is executed in Windows Scripting Host (WSH).
2
2
 
3
+ function CreateVBArray( jsArray) {
4
+ var dict = new ActiveXObject( 'Scripting.Dictionary');
5
+ for( var i = 0; i < jsArray.length; i++)
6
+ dict.Add( i, jsArray[ i]);
7
+ dict.Add( 'length', jsArray.length);
8
+ return Dict2VBArray( dict);
9
+ }
10
+
3
11
  var FSO = new ActiveXObject( 'Scripting.FileSystemObject');
4
12
  var OBJECT_TOSTRING = Object.toString();
5
- var REFERENCES = { // must match node-wsh.js
6
- '0': { type: 'obj', value: WScript},
7
- '1': { type: 'fun', value: GetObject},
8
- '2': { type: 'fun', value: Enumerator}
9
- };
10
- var nextRefId = 3;
13
+ var GLOBAL = this;
14
+ var REFERENCES = {}
15
+ var nextRefId = 0;
11
16
 
12
17
  eval( FSO.OpenTextFile( FSO.BuildPath( FSO.GetParentFolderName( WScript.ScriptFullName), 'json2.js')).ReadAll());
13
18
 
@@ -37,21 +42,12 @@ function decode( encoded) {
37
42
  for( i in encoded.value)
38
43
  decoded[ i] = decode( encoded.value[ i]);
39
44
  return decoded;
40
- case 'objref':
41
- item = REFERENCES[ encoded.value];
42
- if( item === undefined)
43
- throw new Error( 'reference not found: ' + encoded.value);
44
- if( item.type !== 'obj')
45
- throw new Error( 'reference type mismatch: ' + encoded.value);
46
- return item.value;
47
- case 'funref':
45
+ case 'ref':
48
46
  item = REFERENCES[ encoded.value];
49
47
  if( item === undefined)
50
48
  throw new Error( 'reference not found: ' + encoded.value);
51
49
  if( item.type === 'potential-method')
52
50
  throw new Error( 'potentially a method, cannot be evaluated standalone: ' + encoded.value);
53
- if( item.type !== 'fun')
54
- throw new Error( 'reference type mismatch: ' + encoded.value);
55
51
  return item.value;
56
52
  default:
57
53
  throw new Error( 'unknown object type: ' + encoded.type);
@@ -90,19 +86,15 @@ function encode( decoded) {
90
86
  encoded.value[ i] = encode( decoded[ i]);
91
87
  return encoded;
92
88
  }
93
- for( i in REFERENCES)
94
- if( REFERENCES[ i].value === decoded)
95
- return { type: 'objref', value: i};
96
- i = '' + nextRefId++;
97
- REFERENCES[ i] = { type: 'obj', value: decoded};
98
- return { type: 'objref', value: i};
89
+ // warning: intentional fall-through here!
90
+ case 'unknown':
99
91
  case 'function':
100
92
  for( i in REFERENCES)
101
93
  if( REFERENCES[ i].value === decoded)
102
- return { type: 'funref', value: i};
94
+ return { type: 'ref', value: i};
103
95
  i = '' + nextRefId++;
104
- REFERENCES[ i] = { type: 'fun', value: decoded};
105
- return { type: 'funref', value: i};
96
+ REFERENCES[ i] = { value: decoded};
97
+ return { type: 'ref', value: i};
106
98
  case 'symbol':
107
99
  case 'bigint':
108
100
  default:
@@ -115,22 +107,22 @@ function encodePotentialMethod( target, prop) {
115
107
  var item;
116
108
  for( i in REFERENCES) {
117
109
  item = REFERENCES[ i];
118
- if( item.type === 'potential-method' && item.target === target, item.prop === prop)
119
- return { type: 'funref', value: i};
110
+ if( item.type === 'potential-method' && item.target === target && item.prop === prop)
111
+ return { type: 'ref', value: i};
120
112
  }
121
113
  i = '' + nextRefId++;
122
114
  REFERENCES[ i] = { type: 'potential-method', target: target, prop: prop};
123
- return { type: 'funref', value: i};
115
+ return { type: 'ref', value: i};
124
116
  }
125
117
 
126
118
  function decodePotentialMethod( encoded) {
127
119
  var item;
128
- if( typeof encoded === 'object' && encoded.type === 'funref') {
120
+ if( typeof encoded === 'object' && encoded.type === 'ref') {
129
121
  item = REFERENCES[ encoded.value];
130
122
  if( item.type === 'potential-method')
131
- return { 'potential-method': true, target: item.target, prop: item.prop};
123
+ return item;
132
124
  }
133
- return { 'potential-method': false, value: decode( encoded)}
125
+ return { type: 'regular', value: decode( encoded)};
134
126
  }
135
127
 
136
128
  ( function() {
@@ -145,6 +137,9 @@ function decodePotentialMethod( encoded) {
145
137
  try {
146
138
  input = JSON.parse( WScript.StdIn.ReadLine());
147
139
  switch( input[ 0]) {
140
+ case 'global': // [ 'global', name] => [ 'value', value]
141
+ output = [ 'value', encode( GLOBAL[ input[ 1]])];
142
+ break;
148
143
  case 'unref': // [ 'unref', ref] => [ 'done']
149
144
  if( REFERENCES[ input[ 1]] === undefined)
150
145
  throw new Error( 'unknown ref: ' + input[ 1]);
@@ -174,11 +169,11 @@ function decodePotentialMethod( encoded) {
174
169
  target = decodePotentialMethod( input[ 1]);
175
170
  thisArg = decode( input[ 2]);
176
171
  args = decode( input[ 3]);
177
- if( target[ 'potential-method']) {
172
+ if( target.type === 'potential-method') {
178
173
  if( thisArg === undefined)
179
- throw new Error( 'potentially a method, can only be used as such');
174
+ throw new Error( 'potentially a method, use with a "this"');
180
175
  if( thisArg !== target.target)
181
- throw new Error( 'potentially a method, can only be used as such');
176
+ throw new Error( 'potentially a method, "this" has changed');
182
177
  switch( args.length) {
183
178
  case 0: output = [ 'value', encode( target.target[ target.prop]())]; break;
184
179
  case 1: output = [ 'value', encode( target.target[ target.prop]( args[ 0]))]; break;
@@ -0,0 +1,9 @@
1
+ Function Dict2VBArray( dict)
2
+ Dim vbArray()
3
+ ReDim vbArray( dict.Item( "length") - 1)
4
+ Dim I
5
+ For I = 0 To dict.Item( "length") - 1
6
+ vbArray( I) = dict.Item( I)
7
+ Next
8
+ Dict2VBArray = vbArray
9
+ End Function
@@ -0,0 +1,4 @@
1
+ <job>
2
+ <script language="VBScript" src="host.vbs" />
3
+ <script language="JScript" src="host.js" />
4
+ </job>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcticnotes/node-wsh",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "A Node.js package that runs Windows Scripting Host (WSH) as a child process and exposes the resources from the WSH world to the Node.js world",
5
5
  "author": "Paul <paul@arcticnotes.com>",
6
6
  "license": "MIT",