xshell 1.0.19 → 1.0.20
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/i18n/dict.json +15 -0
- package/net.browser.d.ts +42 -18
- package/net.browser.js +140 -151
- package/net.browser.js.map +1 -1
- package/net.d.ts +40 -17
- package/net.js +78 -42
- package/net.js.map +1 -1
- package/package.json +5 -6
- package/server.js +2 -2
- package/server.js.map +1 -1
- package/utils.browser.d.ts +36 -0
- package/utils.browser.js +76 -5
- package/utils.browser.js.map +1 -1
- package/utils.d.ts +37 -1
- package/utils.js +74 -0
- package/utils.js.map +1 -1
package/i18n/dict.json
CHANGED
|
@@ -292,5 +292,20 @@
|
|
|
292
292
|
},
|
|
293
293
|
"连接被关闭": {
|
|
294
294
|
"en": "connection closed"
|
|
295
|
+
},
|
|
296
|
+
"状态码 {{status}}, 非 2xx": {
|
|
297
|
+
"en": "Status code {{status}}, not 2xx"
|
|
298
|
+
},
|
|
299
|
+
"构建 Remote 时 url 和 websocket 最多只能传一个": {
|
|
300
|
+
"en": "When building Remote, only one url and websocket can be passed at most"
|
|
301
|
+
},
|
|
302
|
+
"创建 Remote 时传入的 websocket 连接已断开": {
|
|
303
|
+
"en": "The incoming websocket connection was broken while creating the Remote"
|
|
304
|
+
},
|
|
305
|
+
"传入的 websocket 连接已断开": {
|
|
306
|
+
"en": "The incoming websocket connection was disconnected"
|
|
307
|
+
},
|
|
308
|
+
"尝试释放未锁定的锁,这不应该发生": {
|
|
309
|
+
"en": "Attempt to release an unlocked lock, this should not happen"
|
|
295
310
|
}
|
|
296
311
|
}
|
package/net.browser.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import './prototype.browser.js';
|
|
2
|
+
import { Lock } from './utils.browser.js';
|
|
2
3
|
export interface BasicAuth {
|
|
3
4
|
type: 'basic';
|
|
4
5
|
username: string;
|
|
@@ -14,12 +15,12 @@ export interface RequestOptions {
|
|
|
14
15
|
headers?: Record<string, string> | Headers;
|
|
15
16
|
body?: string | Record<string, any> | ArrayBufferView | ArrayBuffer | Blob | URLSearchParams | FormData;
|
|
16
17
|
type?: 'application/json' | 'application/x-www-form-urlencoded' | 'multipart/form-data';
|
|
18
|
+
encoding?: 'binary';
|
|
17
19
|
retries?: true | number;
|
|
18
20
|
timeout?: number;
|
|
19
21
|
auth?: BasicAuth | BearerAuth;
|
|
20
22
|
cookies?: Record<string, string>;
|
|
21
23
|
cors?: boolean;
|
|
22
|
-
by?: 'fetch' | 'GM_xmlhttpRequest';
|
|
23
24
|
}
|
|
24
25
|
export interface RequestRawOptions extends RequestOptions {
|
|
25
26
|
raw: true;
|
|
@@ -47,11 +48,11 @@ export interface RequestError extends Error {
|
|
|
47
48
|
- body?: http 请求体,可以是 string, Record<string, any> (会自动 JSON.stringify), ArrayBuffer(View), Blob,
|
|
48
49
|
URLSearchParams (type 为 x-www-form-urlencoded), FormData (type 为 form-data)
|
|
49
50
|
- type?: `'application/json'` 有 body 时设置 http 请求头中的 content-type 头
|
|
51
|
+
- encoding?: 设置为 'binary' 时返回 ArrayBuffer 的 body
|
|
50
52
|
- retries?: `false` 可以传入 true (默认 2 次) 或 重试次数
|
|
51
53
|
- timeout?: `5 * 1000`
|
|
52
54
|
- auth?: BasicAuth | BearerAuth
|
|
53
|
-
- raw?: `false` 传入后返回整个 response
|
|
54
|
-
- by: `window.GM_xmlhttpRequest ? 'GM_xmlhttpRequest' : 'fetch'` 发起请求所使用的底层方法 */
|
|
55
|
+
- raw?: `false` 传入后返回整个 response */
|
|
55
56
|
export declare function request(url: string | URL): Promise<string>;
|
|
56
57
|
export declare function request(url: string | URL, options: RequestRawOptions): Promise<Response>;
|
|
57
58
|
export declare function request(url: string | URL, options: RequestOptions & {
|
|
@@ -99,8 +100,7 @@ export type MessageHandler = (message: Message, websocket?: WebSocket) => void |
|
|
|
99
100
|
/** 二进制消息格式
|
|
100
101
|
- json.length (小端序): 4 字节
|
|
101
102
|
- json 数据
|
|
102
|
-
- binary 数据
|
|
103
|
-
*/
|
|
103
|
+
- binary 数据 */
|
|
104
104
|
export interface Message<TData extends any[] = any[]> {
|
|
105
105
|
/** rpc id: 在 rpc 系统中认为是唯一的。用来在单个 websocket 连接上复用多个 rpc 请求。多个相同 id 的 message 组成一个请求流 */
|
|
106
106
|
id?: number;
|
|
@@ -119,8 +119,7 @@ export interface Message<TData extends any[] = any[]> {
|
|
|
119
119
|
里面是可序列化为 json 的 js 变量,或者是 Uint8Array
|
|
120
120
|
Uint8Array 的参数处理后被替换为 Uint8Array.byteLength, 并将下标记录在 bins 中
|
|
121
121
|
|
|
122
|
-
注意: 数组中如果有 undefined 值,传输到对面会变成 null 值
|
|
123
|
-
*/
|
|
122
|
+
注意: 数组中如果有 undefined 值,传输到对面会变成 null 值 */
|
|
124
123
|
data?: TData;
|
|
125
124
|
/** bins: data 中哪些下标对应的原始值是 Uint8Array 类型的,如: [0, 3] */
|
|
126
125
|
bins?: number[];
|
|
@@ -128,35 +127,60 @@ export interface Message<TData extends any[] = any[]> {
|
|
|
128
127
|
/** 通过创建 remote 对象对 websocket rpc 进行抽象
|
|
129
128
|
调用方使用 remote.call 进行调用
|
|
130
129
|
被调方在创建 remote 对象时传入 funcs 注册处理函数,并使用 remote.handle 方法处理 websocket message
|
|
131
|
-
未连接时自动连接,断开后自动重连
|
|
130
|
+
未连接时自动连接,断开后自动重连 (保证执行 send 时已连接,否则报错)
|
|
131
|
+
从设计上与 rpc 状态与底层连接的状态无关 (通过传入 url 创建的),底层连接断开后,只要检测到断线,会尝试重连,如果重连成功,则不影响调用的状态
|
|
132
|
+
@example
|
|
133
|
+
// Zero 继承自 Remote 并通过 call 实现了一些方法
|
|
134
|
+
let zero = new Zero({ local: true })
|
|
135
|
+
|
|
136
|
+
// 一元 rpc
|
|
137
|
+
await zero.repl_ts('1234')
|
|
138
|
+
|
|
139
|
+
// 订阅流
|
|
140
|
+
const id = genid()
|
|
141
|
+
|
|
142
|
+
zero.handlers.set(id, ({ data: [chunk] }: Message<[Uint8Array]>) => {
|
|
143
|
+
term.write(chunk)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
zero.send({ id, func: 'subscribe_stdio' }) */
|
|
132
147
|
export declare class Remote {
|
|
133
|
-
|
|
148
|
+
/** 是否为建立 rpc 连接的发起方 */
|
|
149
|
+
initiator: boolean;
|
|
150
|
+
/** websocket url */
|
|
151
|
+
url?: string;
|
|
134
152
|
/** 主动发起 websocket 连接的客户端 (构造函数传 url) 有这个属性 */
|
|
135
|
-
|
|
153
|
+
lwebsocket?: Lock<WebSocket>;
|
|
136
154
|
/** 通过 rpc message.func 被调用的 rpc 函数 */
|
|
137
155
|
funcs: Record<string, MessageHandler>;
|
|
138
156
|
/** map<id, message handler>: 通过 rpc message.id 找到对应的 handler, unary rpc 接收方不需要设置 handlers, 发送方需要 */
|
|
139
157
|
handlers: Map<number, MessageHandler>;
|
|
140
158
|
print: boolean;
|
|
141
159
|
pconnect: Promise<any>;
|
|
142
|
-
|
|
143
|
-
static parse<TData extends any[] = any[]>(
|
|
160
|
+
/** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
|
|
161
|
+
static parse<TData extends any[] = any[]>(buffer: Uint8Array): Message<TData>;
|
|
144
162
|
static pack({ id, func, data, done, error }: Message): Uint8Array;
|
|
145
|
-
|
|
163
|
+
/** 作为 rpc 发起方,可以通过传入 url 或者 websocket 定义远程 Remote
|
|
164
|
+
作为 rpc 接收方,可以不传 url 和 websocket 定义本地 Remote */
|
|
165
|
+
constructor({ url, funcs, websocket, on_error }?: {
|
|
146
166
|
url?: string;
|
|
147
167
|
funcs?: Remote['funcs'];
|
|
148
168
|
websocket?: WebSocket;
|
|
169
|
+
on_error?(error: WebSocketConnectionError, websocket: WebSocket): void;
|
|
149
170
|
});
|
|
150
|
-
|
|
151
|
-
|
|
171
|
+
on_error?(error: WebSocketConnectionError, websocket: WebSocket): void;
|
|
172
|
+
/** 幂等,保证 websocket 已连接,否则抛出异常,不需要手动调用,在其它方法中已默认自动重连
|
|
173
|
+
作为接收方需要传入使用的 websocket 连接,确保这个这个连接的状态 */
|
|
174
|
+
connect(websocket?: WebSocket): Promise<void>;
|
|
152
175
|
disconnect(): void;
|
|
153
|
-
/** 接收 websocket
|
|
176
|
+
/** 接收 websocket 连接的本地 remote 必传 websocket 参数;发起端选传,如果传了必须等于 this.websocket_lock.resource
|
|
154
177
|
发送或连接出错时自动清理 message.id 对应的 handler */
|
|
155
178
|
send(message: Message, websocket?: WebSocket): Promise<void>;
|
|
156
179
|
/** 处理接收到的 websocket message 并解析, 根据 id dispatch 到对应的 handler 进行处理
|
|
157
180
|
如果 message.done == true 则清理 handler
|
|
158
|
-
如果 handler 返回了值,则包装为 message 发送
|
|
159
|
-
|
|
181
|
+
如果 handler 返回了值,则包装为 message 发送
|
|
182
|
+
使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
|
|
183
|
+
handle(data: Uint8Array, websocket: WebSocket): Promise<void>;
|
|
160
184
|
/** 调用 remote 中的 func, 只适用于最简单的一元 rpc (请求, 响应) */
|
|
161
185
|
call<TReturn extends any[] = any[]>(func: string, args?: any[]): Promise<TReturn>;
|
|
162
186
|
}
|
package/net.browser.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
/// <reference types='tampermonkey' />
|
|
2
1
|
import { t } from './i18n/instance.js';
|
|
3
2
|
import './prototype.browser.js'; // to_time_str()
|
|
4
|
-
import { assert, concat, genid, delay } from './utils.browser.js';
|
|
3
|
+
import { assert, concat, genid, delay, Lock } from './utils.browser.js';
|
|
5
4
|
async function fetch_retry(url, options, timeout, retries = 0, count = 0) {
|
|
6
5
|
try {
|
|
7
6
|
options.signal = AbortSignal.timeout(timeout);
|
|
@@ -19,7 +18,7 @@ async function fetch_retry(url, options, timeout, retries = 0, count = 0) {
|
|
|
19
18
|
}
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
|
-
export async function request(url, { method, queries, headers: _headers, body, type = 'application/json', retries, timeout = 5 * 1000, auth, raw = false, cors,
|
|
21
|
+
export async function request(url, { method, queries, headers: _headers, body, type = 'application/json', encoding, retries, timeout = 5 * 1000, auth, raw = false, cors, } = {}) {
|
|
23
22
|
url = new URL(url, location.href);
|
|
24
23
|
if (queries)
|
|
25
24
|
for (const key in queries) {
|
|
@@ -51,117 +50,71 @@ export async function request(url, { method, queries, headers: _headers, body, t
|
|
|
51
50
|
headers[key] = value;
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
form.set(key, value);
|
|
85
|
-
}
|
|
86
|
-
return form;
|
|
53
|
+
let options = {
|
|
54
|
+
...method ? { method } : {},
|
|
55
|
+
keepalive: true,
|
|
56
|
+
redirect: 'follow',
|
|
57
|
+
credentials: 'include',
|
|
58
|
+
...cors ? { mode: 'cors' } : {},
|
|
59
|
+
headers,
|
|
60
|
+
// --- body
|
|
61
|
+
body: (() => {
|
|
62
|
+
if (body === undefined)
|
|
63
|
+
return;
|
|
64
|
+
switch (type) {
|
|
65
|
+
case 'application/json':
|
|
66
|
+
return typeof body === 'string' ||
|
|
67
|
+
ArrayBuffer.isView(body) ||
|
|
68
|
+
body instanceof ArrayBuffer ||
|
|
69
|
+
body instanceof Blob ?
|
|
70
|
+
body
|
|
71
|
+
:
|
|
72
|
+
JSON.stringify(body);
|
|
73
|
+
case 'application/x-www-form-urlencoded':
|
|
74
|
+
return body instanceof URLSearchParams ? body : new URLSearchParams(body);
|
|
75
|
+
case 'multipart/form-data':
|
|
76
|
+
if (body instanceof FormData)
|
|
77
|
+
return body;
|
|
78
|
+
else {
|
|
79
|
+
let form = new FormData();
|
|
80
|
+
for (const key in body) {
|
|
81
|
+
let value = body[key];
|
|
82
|
+
form.set(key, value);
|
|
87
83
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
error.url = url;
|
|
99
|
-
error.options = options;
|
|
100
|
-
if (response)
|
|
101
|
-
error.response = {
|
|
102
|
-
status: response.status,
|
|
103
|
-
url: response.url,
|
|
104
|
-
headers: response.headers,
|
|
105
|
-
ok: response.ok,
|
|
106
|
-
type: response.type,
|
|
107
|
-
text: await response.text(),
|
|
108
|
-
redirected: response.redirected
|
|
109
|
-
};
|
|
110
|
-
throw error;
|
|
111
|
-
}
|
|
112
|
-
if (raw)
|
|
113
|
-
return response;
|
|
114
|
-
if (!response.body)
|
|
115
|
-
return response.body;
|
|
116
|
-
return response.text();
|
|
84
|
+
return form;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
})(),
|
|
88
|
+
};
|
|
89
|
+
let response;
|
|
90
|
+
try {
|
|
91
|
+
response = await fetch_retry(url, options, timeout, retries);
|
|
92
|
+
if (!response.ok)
|
|
93
|
+
throw Object.assign(new Error(t('状态码 {{status}}, 非 2xx', { status: response.status })), { name: 'StatusCodeError' });
|
|
117
94
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
resolve(response.responseText);
|
|
131
|
-
else
|
|
132
|
-
reject(Object.assign(new Error(`状态码 ${response.status}, 非 2xx`), {
|
|
133
|
-
name: 'StatusCodeError',
|
|
134
|
-
url,
|
|
135
|
-
options,
|
|
136
|
-
response: {
|
|
137
|
-
stauts: response.status,
|
|
138
|
-
url: url.toString(),
|
|
139
|
-
headers: response.responseHeaders,
|
|
140
|
-
ok: false,
|
|
141
|
-
text: response.responseText
|
|
142
|
-
}
|
|
143
|
-
}));
|
|
144
|
-
},
|
|
145
|
-
onerror(response) {
|
|
146
|
-
reject(Object.assign(new Error(response.error), {
|
|
147
|
-
name: 'TimeoutError',
|
|
148
|
-
url: url,
|
|
149
|
-
options,
|
|
150
|
-
response: {
|
|
151
|
-
stauts: response.status,
|
|
152
|
-
url: url.toString(),
|
|
153
|
-
headers: response.responseHeaders,
|
|
154
|
-
ok: false,
|
|
155
|
-
text: response.responseText
|
|
156
|
-
}
|
|
157
|
-
}));
|
|
158
|
-
},
|
|
159
|
-
ontimeout() {
|
|
160
|
-
reject(Object.assign(new Error('GM_xmlhttpRequest 超时'), { name: 'TimeoutError', url, options }));
|
|
161
|
-
},
|
|
95
|
+
catch (error) {
|
|
96
|
+
error.url = url;
|
|
97
|
+
error.options = options;
|
|
98
|
+
if (response)
|
|
99
|
+
error.response = {
|
|
100
|
+
status: response.status,
|
|
101
|
+
url: response.url,
|
|
102
|
+
headers: response.headers,
|
|
103
|
+
ok: response.ok,
|
|
104
|
+
type: response.type,
|
|
105
|
+
text: await response.text(),
|
|
106
|
+
redirected: response.redirected
|
|
162
107
|
};
|
|
163
|
-
|
|
164
|
-
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
if (raw)
|
|
111
|
+
return response;
|
|
112
|
+
if (!response.body)
|
|
113
|
+
return response.body;
|
|
114
|
+
if (encoding === 'binary')
|
|
115
|
+
return response.arrayBuffer();
|
|
116
|
+
else
|
|
117
|
+
return response.text();
|
|
165
118
|
}
|
|
166
119
|
/** 发起 http 请求并将响应体作为 json 解析 */
|
|
167
120
|
export async function request_json(url, options) {
|
|
@@ -272,32 +225,48 @@ export async function connect_websocket(url, { protocols, on_message, on_error,
|
|
|
272
225
|
/** 通过创建 remote 对象对 websocket rpc 进行抽象
|
|
273
226
|
调用方使用 remote.call 进行调用
|
|
274
227
|
被调方在创建 remote 对象时传入 funcs 注册处理函数,并使用 remote.handle 方法处理 websocket message
|
|
275
|
-
未连接时自动连接,断开后自动重连
|
|
228
|
+
未连接时自动连接,断开后自动重连 (保证执行 send 时已连接,否则报错)
|
|
229
|
+
从设计上与 rpc 状态与底层连接的状态无关 (通过传入 url 创建的),底层连接断开后,只要检测到断线,会尝试重连,如果重连成功,则不影响调用的状态
|
|
230
|
+
@example
|
|
231
|
+
// Zero 继承自 Remote 并通过 call 实现了一些方法
|
|
232
|
+
let zero = new Zero({ local: true })
|
|
233
|
+
|
|
234
|
+
// 一元 rpc
|
|
235
|
+
await zero.repl_ts('1234')
|
|
236
|
+
|
|
237
|
+
// 订阅流
|
|
238
|
+
const id = genid()
|
|
239
|
+
|
|
240
|
+
zero.handlers.set(id, ({ data: [chunk] }: Message<[Uint8Array]>) => {
|
|
241
|
+
term.write(chunk)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
zero.send({ id, func: 'subscribe_stdio' }) */
|
|
276
245
|
export class Remote {
|
|
246
|
+
/** 是否为建立 rpc 连接的发起方 */
|
|
247
|
+
initiator;
|
|
248
|
+
/** websocket url */
|
|
277
249
|
url;
|
|
278
250
|
/** 主动发起 websocket 连接的客户端 (构造函数传 url) 有这个属性 */
|
|
279
|
-
|
|
251
|
+
lwebsocket;
|
|
280
252
|
/** 通过 rpc message.func 被调用的 rpc 函数 */
|
|
281
253
|
funcs;
|
|
282
254
|
/** map<id, message handler>: 通过 rpc message.id 找到对应的 handler, unary rpc 接收方不需要设置 handlers, 发送方需要 */
|
|
283
255
|
handlers = new Map();
|
|
284
256
|
print = false;
|
|
285
257
|
pconnect;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
static parse(array_buffer) {
|
|
290
|
-
const buf = new Uint8Array(array_buffer);
|
|
291
|
-
const dv = new DataView(array_buffer);
|
|
258
|
+
/** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
|
|
259
|
+
static parse(buffer) {
|
|
260
|
+
const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
292
261
|
const len_json = dv.getUint32(0, true);
|
|
293
262
|
let offset = 4 + len_json;
|
|
294
|
-
let message = JSON.parse(decoder.decode(
|
|
263
|
+
let message = JSON.parse(decoder.decode(buffer.subarray(4, offset)));
|
|
295
264
|
if (message.bins) {
|
|
296
265
|
const { bins } = message;
|
|
297
266
|
let { data } = message;
|
|
298
267
|
for (const ibin of bins) {
|
|
299
268
|
const len_buf = data[ibin];
|
|
300
|
-
data[ibin] =
|
|
269
|
+
data[ibin] = buffer.subarray(offset, offset + len_buf);
|
|
301
270
|
offset += len_buf;
|
|
302
271
|
}
|
|
303
272
|
}
|
|
@@ -332,50 +301,69 @@ export class Remote {
|
|
|
332
301
|
dv.setUint32(0, str_json.length, true);
|
|
333
302
|
return concat([dv, str_json, ...bufs]);
|
|
334
303
|
}
|
|
335
|
-
|
|
304
|
+
/** 作为 rpc 发起方,可以通过传入 url 或者 websocket 定义远程 Remote
|
|
305
|
+
作为 rpc 接收方,可以不传 url 和 websocket 定义本地 Remote */
|
|
306
|
+
constructor({ url, funcs = {}, websocket, on_error } = {}) {
|
|
307
|
+
assert(!(url && websocket), t('构建 Remote 时 url 和 websocket 最多只能传一个'));
|
|
308
|
+
this.initiator = Boolean(url || websocket);
|
|
336
309
|
this.url = url;
|
|
337
310
|
this.funcs = funcs;
|
|
338
|
-
|
|
311
|
+
if (on_error)
|
|
312
|
+
this.on_error = on_error;
|
|
313
|
+
this.lwebsocket = new Lock(websocket || null);
|
|
314
|
+
}
|
|
315
|
+
on_error(error, websocket) {
|
|
316
|
+
throw error;
|
|
339
317
|
}
|
|
340
|
-
/** 幂等,保证 websocket 已连接,否则抛出异常,不需要手动调用,在其它方法中已默认自动重连
|
|
341
|
-
|
|
342
|
-
|
|
318
|
+
/** 幂等,保证 websocket 已连接,否则抛出异常,不需要手动调用,在其它方法中已默认自动重连
|
|
319
|
+
作为接收方需要传入使用的 websocket 连接,确保这个这个连接的状态 */
|
|
320
|
+
async connect(websocket) {
|
|
321
|
+
if (this.initiator) {
|
|
322
|
+
if (this.lwebsocket.resource?.readyState === WebSocket.OPEN)
|
|
323
|
+
return;
|
|
324
|
+
else if (!this.url)
|
|
325
|
+
throw new Error(t('创建 Remote 时传入的 websocket 连接已断开'));
|
|
326
|
+
// else 等待重连
|
|
327
|
+
}
|
|
328
|
+
else if (websocket.readyState === WebSocket.OPEN)
|
|
343
329
|
return;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
330
|
+
else
|
|
331
|
+
throw new Error(t('传入的 websocket 连接已断开'));
|
|
332
|
+
// 假设有多个请求想要并发连接 websocket, 且此时 websocket 是断开的状态
|
|
333
|
+
// 应该排队依次连接,而不是后续的连接直接使用第一次连接的 promise,后续调用还是应该尝试重连(不止连接一次)
|
|
334
|
+
return this.lwebsocket.request(async (websocket) => {
|
|
335
|
+
// 保存的 rpc 状态在 this.handlers, 与 websocket 无关,因此即使断开重连也不影响 rpc 的运行,即
|
|
336
|
+
// 底层连接断开后自动重连对上层应该是无感知的,除非再次连接时失败
|
|
337
|
+
if (websocket?.readyState === WebSocket.OPEN)
|
|
338
|
+
return;
|
|
339
|
+
else if (!this.url)
|
|
340
|
+
throw new Error(t('创建 Remote 时传入的 websocket 连接已断开'));
|
|
341
|
+
else // 重连
|
|
342
|
+
this.lwebsocket.resource = await connect_websocket(this.url, {
|
|
343
|
+
on_message: (data, websocket) => {
|
|
344
|
+
this.handle(new Uint8Array(data), websocket);
|
|
345
|
+
},
|
|
346
|
+
on_error: this.on_error.bind(this)
|
|
347
|
+
});
|
|
348
348
|
});
|
|
349
|
-
await ptail;
|
|
350
|
-
try {
|
|
351
|
-
if (!this.connected)
|
|
352
|
-
// 保存的 rpc 状态在 this.handlers, 与 websocket 无关,因此即使断开重连也不影响 rpc 的运行,即
|
|
353
|
-
// 底层连接断开后自动重连对上层应该是无感知的,除非再次连接时失败
|
|
354
|
-
this.websocket = await connect_websocket(this.url, { on_message: this.handle.bind(this) });
|
|
355
|
-
}
|
|
356
|
-
finally {
|
|
357
|
-
resolve();
|
|
358
|
-
}
|
|
359
349
|
}
|
|
360
350
|
disconnect() {
|
|
361
|
-
this.
|
|
351
|
+
this.lwebsocket.resource?.close(1000);
|
|
362
352
|
}
|
|
363
|
-
/** 接收 websocket
|
|
353
|
+
/** 接收 websocket 连接的本地 remote 必传 websocket 参数;发起端选传,如果传了必须等于 this.websocket_lock.resource
|
|
364
354
|
发送或连接出错时自动清理 message.id 对应的 handler */
|
|
365
355
|
async send(message, websocket) {
|
|
366
356
|
try {
|
|
367
|
-
if (this.
|
|
368
|
-
assert(!websocket || websocket === this.
|
|
357
|
+
if (this.initiator) {
|
|
358
|
+
assert(!websocket || websocket === this.lwebsocket.resource);
|
|
369
359
|
await this.connect();
|
|
370
|
-
websocket = this.
|
|
371
|
-
}
|
|
372
|
-
else {
|
|
373
|
-
assert(websocket);
|
|
374
|
-
if (websocket.readyState !== WebSocket.OPEN)
|
|
375
|
-
throw new Error(t('remote.send(): websocket client 已断开'));
|
|
360
|
+
websocket = this.lwebsocket.resource;
|
|
376
361
|
}
|
|
362
|
+
else
|
|
363
|
+
await this.connect(websocket);
|
|
377
364
|
if (!message.id)
|
|
378
365
|
message.id = genid();
|
|
366
|
+
// 不需要独占 websocket
|
|
379
367
|
websocket.send(Remote.pack(message));
|
|
380
368
|
}
|
|
381
369
|
catch (error) {
|
|
@@ -386,7 +374,8 @@ export class Remote {
|
|
|
386
374
|
}
|
|
387
375
|
/** 处理接收到的 websocket message 并解析, 根据 id dispatch 到对应的 handler 进行处理
|
|
388
376
|
如果 message.done == true 则清理 handler
|
|
389
|
-
如果 handler 返回了值,则包装为 message 发送
|
|
377
|
+
如果 handler 返回了值,则包装为 message 发送
|
|
378
|
+
使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
|
|
390
379
|
async handle(data, websocket) {
|
|
391
380
|
const message = Remote.parse(data);
|
|
392
381
|
const { id, func, done } = message;
|