geonix 1.10.8 → 1.11.0-beta.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.d.ts +7 -2
- package/package.json +3 -3
- package/src/Service.js +2 -2
- package/src/Stream.js +93 -6
- package/src/Util.js +54 -5
- package/src/WebServer.js +0 -12
package/index.d.ts
CHANGED
|
@@ -124,7 +124,7 @@ export class Service {
|
|
|
124
124
|
#private;
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
127
|
-
* Converts data to stream
|
|
127
|
+
* Converts input data to stream
|
|
128
128
|
*
|
|
129
129
|
* @param {*} data
|
|
130
130
|
* @param {*} automated
|
|
@@ -133,6 +133,8 @@ export class Service {
|
|
|
133
133
|
export function Stream(data: any, tag?: string): any;
|
|
134
134
|
export function isStream(object: any): any;
|
|
135
135
|
export function getReadable(object: any): Promise<any>;
|
|
136
|
+
export function getReadableOverHTTP(object: any): Promise<any>;
|
|
137
|
+
export function getReadableOverNATS(object: any): Promise<any>;
|
|
136
138
|
export function streamToBuffer(object: any): Promise<any>;
|
|
137
139
|
export function streamToString(object: any): Promise<any>;
|
|
138
140
|
export const stats: {};
|
|
@@ -147,6 +149,10 @@ export function parseURL(url: string): {
|
|
|
147
149
|
pass: any;
|
|
148
150
|
token: any;
|
|
149
151
|
};
|
|
152
|
+
export function timeoutAsyncGenerator(target: any, ms: any): {};
|
|
153
|
+
export function waitForAbort(abortSignal: any, result: any): Promise<any>;
|
|
154
|
+
export function abortableAsyncGenerator(target: any, abortSignal: any): {};
|
|
155
|
+
export function getNetworkAddresses(): any[];
|
|
150
156
|
export function sleep(delay: number): Promise<any>;
|
|
151
157
|
export function picoid(size?: number): any;
|
|
152
158
|
export function hash(data: string | Buffer): any;
|
|
@@ -165,7 +171,6 @@ export function ServeStatic(root: any, options?: {}): any;
|
|
|
165
171
|
export const webserver: WebServer;
|
|
166
172
|
declare class WebServer {
|
|
167
173
|
start(): Promise<void>;
|
|
168
|
-
getAddresses(): any[];
|
|
169
174
|
getPort(): any;
|
|
170
175
|
router(): any;
|
|
171
176
|
waitUntilReady(): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "geonix",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "",
|
|
6
6
|
"bin": {
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"express-async-errors": "^3.1.1",
|
|
21
21
|
"express-ws": "^5.0.2",
|
|
22
22
|
"multer": "^1.4.5-lts.1",
|
|
23
|
-
"nats": "^2.
|
|
23
|
+
"nats": "^2.19.0",
|
|
24
24
|
"semver": "^7.5.4",
|
|
25
|
-
"ws": "^8.
|
|
25
|
+
"ws": "^8.16.0"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"registry": "https://registry.npmjs.org/"
|
package/src/Service.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { codec, connection } from './Connection.js'
|
|
2
|
-
import { picoid, sleep, hash, getSecondsSinceMidnight, OverlayObject, GeonixVersion } from './Util.js'
|
|
2
|
+
import { picoid, sleep, hash, getSecondsSinceMidnight, OverlayObject, GeonixVersion, getNetworkAddresses } from './Util.js'
|
|
3
3
|
import { webserver } from './WebServer.js'
|
|
4
4
|
import { createConnection } from 'net'
|
|
5
5
|
import { EOL } from 'os'
|
|
@@ -67,7 +67,7 @@ export class Service {
|
|
|
67
67
|
.filter(methodName => !protectedMethodNames.includes(methodName))
|
|
68
68
|
.filter(methodName => !methodName.startsWith('$')),
|
|
69
69
|
// IP addresses
|
|
70
|
-
a:
|
|
70
|
+
a: getNetworkAddresses().map(address => `${address}:${webserver.getPort()}`)
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// check if method takes context as first argument
|
package/src/Stream.js
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import { Readable } from 'stream'
|
|
2
2
|
import { connection } from './Connection.js'
|
|
3
|
-
import { picoid, StreamChunker } from './Util.js'
|
|
3
|
+
import { abortableAsyncGenerator, createServerAtFreePort, getNetworkAddresses, picoid, StreamChunker } from './Util.js'
|
|
4
|
+
import http from 'http'
|
|
4
5
|
|
|
5
6
|
const CHUNK_SIZE = 1024 * 128
|
|
7
|
+
const STREAM_TIMEOUT = 120000
|
|
6
8
|
|
|
7
9
|
export const stats = {}
|
|
8
10
|
|
|
11
|
+
// HTTP stream server
|
|
12
|
+
const streams = {}
|
|
13
|
+
const { port: streamServerPort } = await createServerAtFreePort(http, (req, res) => {
|
|
14
|
+
const stream = streams[decodeURIComponent(req.url.substring(1))]
|
|
15
|
+
if (stream) {
|
|
16
|
+
stream.pipe(res)
|
|
17
|
+
} else {
|
|
18
|
+
res.statusCode = 404
|
|
19
|
+
res.end()
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
}, 40000)
|
|
23
|
+
|
|
9
24
|
/**
|
|
10
|
-
* Converts data to stream
|
|
25
|
+
* Converts input data to stream
|
|
11
26
|
*
|
|
12
27
|
* @param {*} data
|
|
13
28
|
* @param {*} automated
|
|
@@ -18,6 +33,7 @@ export function Stream(data, tag = '_') {
|
|
|
18
33
|
return data
|
|
19
34
|
|
|
20
35
|
const id = picoid()
|
|
36
|
+
const result = { $: 'stream', id }
|
|
21
37
|
let readable = data
|
|
22
38
|
|
|
23
39
|
// convert Buffer or string to a Readable
|
|
@@ -31,10 +47,18 @@ export function Stream(data, tag = '_') {
|
|
|
31
47
|
|
|
32
48
|
stats[tag] = stats[tag] !== undefined ? stats[tag] + 1 : 1
|
|
33
49
|
|
|
34
|
-
const
|
|
50
|
+
const acDeliveryOverNATS = new AbortController();
|
|
51
|
+
|
|
52
|
+
const deliverOverNATS = async () => {
|
|
35
53
|
const control = await connection.subscribe(`gx2.stream.${id}.a`, { max: 1 })
|
|
36
54
|
|
|
37
|
-
|
|
55
|
+
// abort if no request to start streaming
|
|
56
|
+
const timeout = setTimeout(() => {
|
|
57
|
+
acDeliveryOverNATS.abort()
|
|
58
|
+
}, STREAM_TIMEOUT)
|
|
59
|
+
|
|
60
|
+
// deliver the stream
|
|
61
|
+
for await (const event of abortableAsyncGenerator(control, acDeliveryOverNATS.signal)) {
|
|
38
62
|
if (event.data.length === 0) {
|
|
39
63
|
readable.on('data', chunk => connection.publishRaw(`gx2.stream.${id}.b`, chunk))
|
|
40
64
|
readable.on('close', () => {
|
|
@@ -42,11 +66,37 @@ export function Stream(data, tag = '_') {
|
|
|
42
66
|
stats[tag]--
|
|
43
67
|
})
|
|
44
68
|
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// cleanup
|
|
72
|
+
clearTimeout(timeout)
|
|
73
|
+
await control.drain()
|
|
74
|
+
await connection.unsubscribe(control)
|
|
45
75
|
}
|
|
46
76
|
|
|
47
|
-
|
|
77
|
+
const deliverOverHTTP = async () => {
|
|
78
|
+
// stop listening for NATS request
|
|
79
|
+
acDeliveryOverNATS.abort()
|
|
80
|
+
|
|
81
|
+
// register stream as available over HTTP
|
|
82
|
+
streams[id] = readable
|
|
48
83
|
|
|
49
|
-
|
|
84
|
+
// cleanup
|
|
85
|
+
readable.on('finish', () => {
|
|
86
|
+
delete streams[id]
|
|
87
|
+
stats[tag]--
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
deliverOverNATS()
|
|
92
|
+
|
|
93
|
+
if (streamServerPort != -1) {
|
|
94
|
+
result.a = getNetworkAddresses()
|
|
95
|
+
result.p = streamServerPort;
|
|
96
|
+
deliverOverHTTP()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result
|
|
50
100
|
}
|
|
51
101
|
|
|
52
102
|
export function isStream(object) {
|
|
@@ -57,6 +107,43 @@ export async function getReadable(object) {
|
|
|
57
107
|
if (!isStream(object))
|
|
58
108
|
return object
|
|
59
109
|
|
|
110
|
+
try {
|
|
111
|
+
return await getReadableOverHTTP(object);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
return getReadableOverNATS(object);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function getReadableOverHTTP(object) {
|
|
118
|
+
if (!object.a) {
|
|
119
|
+
throw new Error('Stream is not available over HTTP')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (const address of object.a) {
|
|
123
|
+
try {
|
|
124
|
+
const response = await new Promise((resolve, reject) => {
|
|
125
|
+
const request = http.request(`http://${address}:${object.p}/${object.id}`, { method: 'GET', timeout: 5000 })
|
|
126
|
+
request.on('response', (response) => {
|
|
127
|
+
resolve(response)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
request.on('error', (e) => {
|
|
131
|
+
reject(e)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
request.end()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
return response
|
|
138
|
+
} catch (e) {
|
|
139
|
+
console.error(`Stream.getReadableOverHTTP.error:`, e)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
throw new Error('No data')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function getReadableOverNATS(object) {
|
|
60
147
|
const readable = new Readable({ read: () => null })
|
|
61
148
|
const subscription = await connection.subscribe(`gx2.stream.${object.id}.b`)
|
|
62
149
|
|
package/src/Util.js
CHANGED
|
@@ -3,11 +3,9 @@ import { URL, fileURLToPath } from 'url'
|
|
|
3
3
|
import { readFile } from 'fs/promises'
|
|
4
4
|
import { join } from 'path'
|
|
5
5
|
import { Transform } from 'node:stream'
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import * as net from 'net'
|
|
10
|
-
|
|
6
|
+
import { networkInterfaces } from 'os'
|
|
7
|
+
import http from 'http'
|
|
8
|
+
import https from 'http'
|
|
11
9
|
/**
|
|
12
10
|
* Wait for {delay} ms
|
|
13
11
|
* @param {number} delay
|
|
@@ -196,3 +194,54 @@ export const StreamChunker = (chunkSize = 65536) => {
|
|
|
196
194
|
|
|
197
195
|
return chunker;
|
|
198
196
|
}
|
|
197
|
+
|
|
198
|
+
export async function* timeoutAsyncGenerator(target, ms) {
|
|
199
|
+
const iterator = target[Symbol.asyncIterator]()
|
|
200
|
+
|
|
201
|
+
while (true) {
|
|
202
|
+
const { value, done } = await Promise.race([iterator.next(), sleep(ms)]) || {};
|
|
203
|
+
if (done || (value === undefined && done === undefined)) {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
yield value;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function waitForAbort(abortSignal, result) {
|
|
212
|
+
return new Promise(resolve => {
|
|
213
|
+
if (abortSignal.aborted) {
|
|
214
|
+
resolve(result)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
abortSignal.addEventListener('abort', () => {
|
|
218
|
+
console.log('aborted')
|
|
219
|
+
resolve(result)
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function* abortableAsyncGenerator(target, abortSignal) {
|
|
225
|
+
const iterator = target[Symbol.asyncIterator]()
|
|
226
|
+
|
|
227
|
+
while (true) {
|
|
228
|
+
const { value, done } = await Promise.race([iterator.next(), waitForAbort(abortSignal)]) || {};
|
|
229
|
+
if (done || (value === undefined && done === undefined)) {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
yield value;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function getNetworkAddresses() {
|
|
238
|
+
const list = []
|
|
239
|
+
const interfaces = networkInterfaces()
|
|
240
|
+
|
|
241
|
+
for (let interfaceAddresses of Object.values(interfaces))
|
|
242
|
+
for (let addressObject of interfaceAddresses)
|
|
243
|
+
if (addressObject.family === 'IPv4')
|
|
244
|
+
list.push(addressObject.address)
|
|
245
|
+
|
|
246
|
+
return list
|
|
247
|
+
}
|
package/src/WebServer.js
CHANGED
|
@@ -102,18 +102,6 @@ class WebServer {
|
|
|
102
102
|
this.#ready = true
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
getAddresses() {
|
|
106
|
-
const list = []
|
|
107
|
-
const interfaces = networkInterfaces()
|
|
108
|
-
|
|
109
|
-
for (let interfaceAddresses of Object.values(interfaces))
|
|
110
|
-
for (let addressObject of interfaceAddresses)
|
|
111
|
-
if (addressObject.family === 'IPv4')
|
|
112
|
-
list.push(addressObject.address)
|
|
113
|
-
|
|
114
|
-
return list
|
|
115
|
-
}
|
|
116
|
-
|
|
117
105
|
getPort() {
|
|
118
106
|
return this.#port
|
|
119
107
|
}
|