@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 +3 -3
- package/lib/node/node-wsh.js +112 -96
- package/lib/wsh/host.js +14 -6
- package/package.json +1 -1
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
|
|
22
|
-
const WScript =
|
|
21
|
+
const WSH = await WindowsScriptingHost.connect();
|
|
22
|
+
const WScript = WSH.global( 'WScript');
|
|
23
23
|
console.log(WScript.Version);
|
|
24
|
-
await
|
|
24
|
+
await WSH.disconnect();
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Dependencies
|
package/lib/node/node-wsh.js
CHANGED
|
@@ -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})
|
|
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
|
|
22
|
+
#proxy2ref = new WeakMap();
|
|
72
23
|
|
|
73
24
|
#objectHandler = {
|
|
74
25
|
|
|
75
|
-
|
|
26
|
+
wsh: this,
|
|
76
27
|
|
|
77
28
|
get( target, prop) {
|
|
78
29
|
if( prop === Symbol.toPrimitive)
|
|
79
|
-
return () => `ref#${
|
|
80
|
-
const encodedTarget = this.
|
|
81
|
-
const encodedProp = this.
|
|
82
|
-
const output = JSON.parse( this.
|
|
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.
|
|
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
|
-
|
|
56
|
+
wsh: this,
|
|
94
57
|
|
|
95
58
|
get( target, prop) {
|
|
96
59
|
if( prop === Symbol.toPrimitive)
|
|
97
|
-
return () => `ref#${
|
|
60
|
+
return () => `ref#${ target.ref}`;
|
|
98
61
|
return undefined;
|
|
99
62
|
},
|
|
100
63
|
|
|
101
64
|
apply( target, thisArg, argumentList) {
|
|
102
|
-
const encodedTarget = this.
|
|
103
|
-
const encodedThisArg = this.
|
|
104
|
-
const encodedArgumentList = this.
|
|
105
|
-
const output = JSON.parse( this.
|
|
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.
|
|
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.
|
|
115
|
-
const encodedArgumentList = this.
|
|
116
|
-
const output = JSON.parse( this.
|
|
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.
|
|
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
|
|
88
|
+
constructor( syncline) {
|
|
89
|
+
super();
|
|
126
90
|
this.#syncline = syncline;
|
|
127
|
-
this.#
|
|
128
|
-
this.#
|
|
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
|
|
132
|
-
|
|
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
|
|
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():
|
|
131
|
+
const target = handler === this.#objectHandler? new RemoteObject( ref): new RemoteFunction( ref);
|
|
149
132
|
const newProxy = new Proxy( target, handler);
|
|
150
|
-
target
|
|
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
|
|
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
|
-
|
|
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
|
|
211
|
+
return this.#getOrCreateObject( encoded.value);
|
|
238
212
|
case 'funref':
|
|
239
|
-
return this
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
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.
|
|
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",
|