@arcticnotes/node-wsh 0.0.8 → 0.0.10

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
@@ -5,8 +5,6 @@ import { Syncline} from "@arcticnotes/syncline";
5
5
  const COMMAND = 'cscript.exe';
6
6
  const ARGS = [ '//E:jscript', '//NoLogo'];
7
7
  const SCRIPT_FILE = PATH.join( PATH.dirname( import.meta.dirname), 'wsh', 'host.js');
8
- const PROXY = Symbol();
9
- const TRACE_REF = 1;
10
8
 
11
9
  export class WindowsScriptingHost extends EventEmitter {
12
10
 
@@ -15,73 +13,38 @@ 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;
69
20
  #finalizer = new FinalizationRegistry( this.#finalized.bind( this));
70
21
  #ref2proxy = new Map();
71
- #proxy2ref = new Map();
22
+ #proxy2ref = new WeakMap();
72
23
 
73
24
  #objectHandler = {
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]);
36
+ case 'error': throw new Error( output[ 1]);
37
+ default: throw new Error( `unknown status: ${ output[ 0]}`);
38
+ }
39
+ },
40
+
41
+ set( target, prop, value) {
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])));
46
+ switch( output[ 0]) {
47
+ case 'set': return;
85
48
  case 'error': throw new Error( output[ 1]);
86
49
  default: throw new Error( `unknown status: ${ output[ 0]}`);
87
50
  }
@@ -90,85 +53,96 @@ class Proxies {
90
53
 
91
54
  #functionHandler = {
92
55
 
93
- proxies: this,
56
+ wsh: this,
94
57
 
95
58
  get( target, prop) {
96
59
  if( prop === Symbol.toPrimitive)
97
- return () => `ref#${ this.proxies.#proxy2ref.get( target[ PROXY])}`;
60
+ return () => `ref#${ target.ref}`;
98
61
  return undefined;
99
62
  },
100
63
 
101
64
  apply( target, thisArg, argumentList) {
102
- const encodedTarget = this.proxies.#encode( target[ PROXY]);
103
- const encodedThisArg = this.proxies.#encode( thisArg);
104
- const encodedArgumentList = this.proxies.#encode( argumentList);
105
- const output = JSON.parse( this.proxies.#syncline.exchange( JSON.stringify( [ 'apply', encodedTarget, encodedThisArg, encodedArgumentList])));
65
+ const encodedTarget = this.wsh.#encode( target.proxy);
66
+ const encodedThisArg = this.wsh.#encode( thisArg);
67
+ const encodedArgumentList = this.wsh.#encode( argumentList);
68
+ const output = JSON.parse( this.wsh.#syncline.exchange( JSON.stringify( [ 'apply', encodedTarget, encodedThisArg, encodedArgumentList])));
106
69
  switch( output[ 0]) {
107
- case 'value': return this.proxies.#decode( output[ 1]);
70
+ case 'value': return this.wsh.#decode( output[ 1]);
108
71
  case 'error': throw new Error( output[ 1]);
109
72
  default: throw new Error( `unknown status: ${ output[ 0]}`);
110
73
  }
111
74
  },
112
75
 
113
76
  construct( target, argumentList) {
114
- const encodedTarget = this.proxies.#encode( target[ PROXY]);
115
- const encodedArgumentList = this.proxies.#encode( argumentList);
116
- const output = JSON.parse( this.proxies.#syncline.exchange( JSON.stringify( [ 'construct', encodedTarget, encodedArgumentList])));
77
+ const encodedTarget = this.wsh.#encode( target.proxy);
78
+ const encodedArgumentList = this.wsh.#encode( argumentList);
79
+ const output = JSON.parse( this.wsh.#syncline.exchange( JSON.stringify( [ 'construct', encodedTarget, encodedArgumentList])));
117
80
  switch( output[ 0]) {
118
- case 'value': return this.proxies.#decode( output[ 1]);
81
+ case 'value': return this.wsh.#decode( output[ 1]);
119
82
  case 'error': throw new Error( output[ 1]);
120
83
  default: throw new Error( `unknown status: ${ output[ 0]}`);
121
84
  }
122
85
  },
123
86
  };
124
87
 
125
- constructor( syncline, options, eventEmitter) {
88
+ constructor( syncline) {
89
+ super();
126
90
  this.#syncline = syncline;
127
- this.#trace = options.trace;
128
- this.#eventEmitter = eventEmitter;
91
+ this.#syncline.on( 'stderr', line => console.log( 'wsh:', line));
92
+ this.#syncline.on( 'stdout', line => console.log( 'wsh:', line));
129
93
  }
130
94
 
131
- get count() {
132
- return this.#ref2proxy.size;
95
+ get remoteObjects() {
96
+ const proxies = this;
97
+ return {
98
+ get count() {
99
+ return proxies.#ref2proxy.size;
100
+ },
101
+ };
102
+ }
103
+
104
+ global( name) {
105
+ const output = JSON.parse( this.#syncline.exchange( JSON.stringify( [ 'global', name])));
106
+ switch( output[ 0]) {
107
+ case 'value': return this.#decode( output[ 1]);
108
+ case 'error': throw new Error( output[ 1]);
109
+ default: throw new Error( `unknown status: ${ output[ 0]}`);
110
+ }
111
+ }
112
+
113
+ async disconnect() {
114
+ await this.#syncline.close();
133
115
  }
134
116
 
135
- getOrCreateObject( ref) {
117
+ #getOrCreateObject( ref) {
136
118
  return this.#getOrCreate( ref, this.#objectHandler);
137
119
  }
138
120
 
139
- getOrCreateFunction( ref) {
121
+ #getOrCreateFunction( ref) {
140
122
  return this.#getOrCreate( ref, this.#functionHandler);
141
123
  }
142
124
 
143
125
  #getOrCreate( ref, handler) {
144
- const existingProxy = this.#ref2proxy.get( ref);
126
+ const existingWeakRef = this.#ref2proxy.get( ref);
127
+ const existingProxy = existingWeakRef && existingWeakRef.deref();
145
128
  if( existingProxy)
146
129
  return existingProxy;
147
130
 
148
- const target = handler === this.#objectHandler? new RemoteObject(): function() {};
131
+ const target = handler === this.#objectHandler? new RemoteObject( ref): new RemoteFunction( ref);
149
132
  const newProxy = new Proxy( target, handler);
150
- target[ PROXY] = newProxy;
151
- this.#ref2proxy.set( ref, newProxy);
133
+ target.proxy = newProxy;
134
+ this.#ref2proxy.set( ref, new WeakRef( newProxy)); // may be overwriting a dead WeakRef
152
135
  this.#proxy2ref.set( newProxy, ref);
153
136
  this.#finalizer.register( newProxy, ref);
154
- this.#eventEmitter.emit( 'ref', ref, newProxy);
137
+ this.emit( 'ref', ref, newProxy);
155
138
  return newProxy;
156
139
  }
157
140
 
158
141
  #finalized( ref) {
142
+ if( this.#ref2proxy.get( ref).deref() === undefined) // otherwise, it's overwritten by a refreshed proxy
143
+ this.#ref2proxy.delete( ref);
159
144
  const output = this.#syncline.exchange( JSON.stringify( [ 'unref', ref]));
160
- if( this.#trace >= TRACE_REF)
161
- switch( output[ 0]) {
162
- case 'error':
163
- console.log( `failed to unref: ${ ref}`);
164
- break;
165
- case 'done':
166
- console.log( `unreferenced: ${ ref}`);
167
- break;
168
- default:
169
- console.log( `unknown response: ${ output[ 0]}`);
170
- }
171
- this.#eventEmitter.emit( 'unref', ref);
145
+ this.emit( 'unref', ref);
172
146
  }
173
147
 
174
148
  #encode( decoded) {
@@ -190,7 +164,7 @@ class Proxies {
190
164
  }
191
165
  if( decoded instanceof RemoteObject) {
192
166
  const objref = this.#proxy2ref.get( decoded);
193
- if( objref === undefined)
167
+ if( objref === undefined) // not because garbage-collected, because clearly `decoded` is still alive
194
168
  throw new Error( `remote object reference not found: ${ decoded}`);
195
169
  return { type: 'objref', value: objref};
196
170
  }
@@ -200,7 +174,7 @@ class Proxies {
200
174
  return encoded;
201
175
  case 'function':
202
176
  const funref = this.#proxy2ref.get( decoded);
203
- if( funref === undefined)
177
+ if( funref === undefined) // not because garbage-collected, because clearly `decoded` is still alive
204
178
  throw new Error( `functions from node are disallowed: ${ decoded}`);
205
179
  return { type: 'funref', value: funref};
206
180
  case 'bigint':
@@ -234,9 +208,9 @@ class Proxies {
234
208
  decoded[ name] = this.#decode( value);
235
209
  return decoded;
236
210
  case 'objref':
237
- return this.getOrCreateObject( encoded.value);
211
+ return this.#getOrCreateObject( encoded.value);
238
212
  case 'funref':
239
- return this.getOrCreateFunction( encoded.value);
213
+ return this.#getOrCreateFunction( encoded.value);
240
214
  default:
241
215
  throw new Error( `illegal value: ${ encoded}`);
242
216
  }
@@ -251,4 +225,46 @@ class Proxies {
251
225
  }
252
226
 
253
227
  class RemoteObject {
228
+
229
+ #ref;
230
+ #proxy;
231
+
232
+ constructor( ref) {
233
+ this.#ref = ref;
234
+ }
235
+
236
+ get ref() {
237
+ return this.#ref;
238
+ }
239
+
240
+ get proxy() {
241
+ return this.#proxy;
242
+ }
243
+
244
+ set proxy( proxy) {
245
+ this.#proxy = proxy;
246
+ }
247
+ }
248
+
249
+ class RemoteFunction extends Function {
250
+
251
+ #ref;
252
+ #proxy;
253
+
254
+ constructor( ref) {
255
+ super();
256
+ this.#ref = ref;
257
+ }
258
+
259
+ get ref() {
260
+ return this.#ref;
261
+ }
262
+
263
+ get proxy() {
264
+ return this.#proxy;
265
+ }
266
+
267
+ set proxy( proxy) {
268
+ this.#proxy = proxy;
269
+ }
254
270
  }
package/lib/wsh/host.js CHANGED
@@ -2,12 +2,9 @@
2
2
 
3
3
  var FSO = new ActiveXObject( 'Scripting.FileSystemObject');
4
4
  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;
5
+ var GLOBAL = this;
6
+ var REFERENCES = {}
7
+ var nextRefId = 0;
11
8
 
12
9
  eval( FSO.OpenTextFile( FSO.BuildPath( FSO.GetParentFolderName( WScript.ScriptFullName), 'json2.js')).ReadAll());
13
10
 
@@ -138,12 +135,16 @@ function decodePotentialMethod( encoded) {
138
135
  var output;
139
136
  var target;
140
137
  var prop;
138
+ var value;
141
139
  var thisArg;
142
140
  var args;
143
141
  while( !WScript.StdIn.AtEndOfLine)
144
142
  try {
145
143
  input = JSON.parse( WScript.StdIn.ReadLine());
146
144
  switch( input[ 0]) {
145
+ case 'global': // [ 'global', name] => [ 'value', value]
146
+ output = [ 'value', encode( GLOBAL[ input[ 1]])];
147
+ break;
147
148
  case 'unref': // [ 'unref', ref] => [ 'done']
148
149
  if( REFERENCES[ input[ 1]] === undefined)
149
150
  throw new Error( 'unknown ref: ' + input[ 1]);
@@ -162,6 +163,13 @@ function decodePotentialMethod( encoded) {
162
163
  output = [ 'value', encodePotentialMethod( target, prop)];
163
164
  }
164
165
  break;
166
+ case 'set': // [ 'set', target, prop, value] => [ 'set']
167
+ target = decode( input[ 1]);
168
+ prop = decode( input[ 2]);
169
+ value = decode( input[ 3]);
170
+ target[ prop] = value;
171
+ output = [ 'set'];
172
+ break;
165
173
  case 'apply': // [ 'apply', target, thisArg, argumentList] => [ 'value', value]
166
174
  target = decodePotentialMethod( input[ 1]);
167
175
  thisArg = decode( input[ 2]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcticnotes/node-wsh",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
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",