@arcticnotes/node-wsh 0.0.9 → 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,83 +13,36 @@ 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]);
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]);
@@ -102,85 +53,96 @@ class Proxies {
102
53
 
103
54
  #functionHandler = {
104
55
 
105
- proxies: this,
56
+ wsh: this,
106
57
 
107
58
  get( target, prop) {
108
59
  if( prop === Symbol.toPrimitive)
109
- return () => `ref#${ this.proxies.#proxy2ref.get( target[ PROXY])}`;
60
+ return () => `ref#${ target.ref}`;
110
61
  return undefined;
111
62
  },
112
63
 
113
64
  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])));
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])));
118
69
  switch( output[ 0]) {
119
- case 'value': return this.proxies.#decode( output[ 1]);
70
+ case 'value': return this.wsh.#decode( output[ 1]);
120
71
  case 'error': throw new Error( output[ 1]);
121
72
  default: throw new Error( `unknown status: ${ output[ 0]}`);
122
73
  }
123
74
  },
124
75
 
125
76
  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])));
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])));
129
80
  switch( output[ 0]) {
130
- case 'value': return this.proxies.#decode( output[ 1]);
81
+ case 'value': return this.wsh.#decode( output[ 1]);
131
82
  case 'error': throw new Error( output[ 1]);
132
83
  default: throw new Error( `unknown status: ${ output[ 0]}`);
133
84
  }
134
85
  },
135
86
  };
136
87
 
137
- constructor( syncline, options, eventEmitter) {
88
+ constructor( syncline) {
89
+ super();
138
90
  this.#syncline = syncline;
139
- this.#trace = options.trace;
140
- this.#eventEmitter = eventEmitter;
91
+ this.#syncline.on( 'stderr', line => console.log( 'wsh:', line));
92
+ this.#syncline.on( 'stdout', line => console.log( 'wsh:', line));
141
93
  }
142
94
 
143
- get count() {
144
- 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();
145
115
  }
146
116
 
147
- getOrCreateObject( ref) {
117
+ #getOrCreateObject( ref) {
148
118
  return this.#getOrCreate( ref, this.#objectHandler);
149
119
  }
150
120
 
151
- getOrCreateFunction( ref) {
121
+ #getOrCreateFunction( ref) {
152
122
  return this.#getOrCreate( ref, this.#functionHandler);
153
123
  }
154
124
 
155
125
  #getOrCreate( ref, handler) {
156
- const existingProxy = this.#ref2proxy.get( ref);
126
+ const existingWeakRef = this.#ref2proxy.get( ref);
127
+ const existingProxy = existingWeakRef && existingWeakRef.deref();
157
128
  if( existingProxy)
158
129
  return existingProxy;
159
130
 
160
- const target = handler === this.#objectHandler? new RemoteObject(): function() {};
131
+ const target = handler === this.#objectHandler? new RemoteObject( ref): new RemoteFunction( ref);
161
132
  const newProxy = new Proxy( target, handler);
162
- target[ PROXY] = newProxy;
163
- this.#ref2proxy.set( ref, newProxy);
133
+ target.proxy = newProxy;
134
+ this.#ref2proxy.set( ref, new WeakRef( newProxy)); // may be overwriting a dead WeakRef
164
135
  this.#proxy2ref.set( newProxy, ref);
165
136
  this.#finalizer.register( newProxy, ref);
166
- this.#eventEmitter.emit( 'ref', ref, newProxy);
137
+ this.emit( 'ref', ref, newProxy);
167
138
  return newProxy;
168
139
  }
169
140
 
170
141
  #finalized( ref) {
142
+ if( this.#ref2proxy.get( ref).deref() === undefined) // otherwise, it's overwritten by a refreshed proxy
143
+ this.#ref2proxy.delete( ref);
171
144
  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);
145
+ this.emit( 'unref', ref);
184
146
  }
185
147
 
186
148
  #encode( decoded) {
@@ -202,7 +164,7 @@ class Proxies {
202
164
  }
203
165
  if( decoded instanceof RemoteObject) {
204
166
  const objref = this.#proxy2ref.get( decoded);
205
- if( objref === undefined)
167
+ if( objref === undefined) // not because garbage-collected, because clearly `decoded` is still alive
206
168
  throw new Error( `remote object reference not found: ${ decoded}`);
207
169
  return { type: 'objref', value: objref};
208
170
  }
@@ -212,7 +174,7 @@ class Proxies {
212
174
  return encoded;
213
175
  case 'function':
214
176
  const funref = this.#proxy2ref.get( decoded);
215
- if( funref === undefined)
177
+ if( funref === undefined) // not because garbage-collected, because clearly `decoded` is still alive
216
178
  throw new Error( `functions from node are disallowed: ${ decoded}`);
217
179
  return { type: 'funref', value: funref};
218
180
  case 'bigint':
@@ -246,9 +208,9 @@ class Proxies {
246
208
  decoded[ name] = this.#decode( value);
247
209
  return decoded;
248
210
  case 'objref':
249
- return this.getOrCreateObject( encoded.value);
211
+ return this.#getOrCreateObject( encoded.value);
250
212
  case 'funref':
251
- return this.getOrCreateFunction( encoded.value);
213
+ return this.#getOrCreateFunction( encoded.value);
252
214
  default:
253
215
  throw new Error( `illegal value: ${ encoded}`);
254
216
  }
@@ -263,4 +225,46 @@ class Proxies {
263
225
  }
264
226
 
265
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
+ }
266
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
 
@@ -145,6 +142,9 @@ function decodePotentialMethod( encoded) {
145
142
  try {
146
143
  input = JSON.parse( WScript.StdIn.ReadLine());
147
144
  switch( input[ 0]) {
145
+ case 'global': // [ 'global', name] => [ 'value', value]
146
+ output = [ 'value', encode( GLOBAL[ input[ 1]])];
147
+ break;
148
148
  case 'unref': // [ 'unref', ref] => [ 'done']
149
149
  if( REFERENCES[ input[ 1]] === undefined)
150
150
  throw new Error( 'unknown ref: ' + input[ 1]);
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.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",