@arcblock/event-hub 1.28.8 → 1.29.0
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/esm/client.d.mts +43 -0
- package/esm/client.mjs +134 -0
- package/esm/constant.d.mts +7 -0
- package/esm/constant.mjs +8 -0
- package/esm/index.d.mts +7 -0
- package/esm/index.mjs +28 -0
- package/esm/rpc.d.mts +51 -0
- package/esm/rpc.mjs +106 -0
- package/esm/server-abtnode.d.mts +6 -0
- package/esm/server-abtnode.mjs +11 -0
- package/esm/server.d.mts +55 -0
- package/esm/server.mjs +142 -0
- package/esm/single.d.mts +10 -0
- package/esm/single.mjs +16 -0
- package/lib/_virtual/rolldown_runtime.cjs +29 -0
- package/lib/client.cjs +139 -0
- package/lib/client.d.cts +43 -0
- package/lib/constant.cjs +12 -0
- package/lib/constant.d.cts +7 -0
- package/lib/index.cjs +31 -0
- package/lib/index.d.cts +7 -0
- package/lib/rpc.cjs +110 -0
- package/lib/rpc.d.cts +51 -0
- package/lib/server-abtnode.cjs +12 -0
- package/lib/server-abtnode.d.cts +6 -0
- package/lib/server.cjs +147 -0
- package/lib/server.d.cts +55 -0
- package/lib/single.cjs +19 -0
- package/lib/single.d.cts +10 -0
- package/package.json +33 -6
- package/lib/client-fallback.js +0 -13
- package/lib/client.js +0 -183
- package/lib/constant.js +0 -6
- package/lib/index.js +0 -26
- package/lib/rpc.js +0 -119
- package/lib/server-abtnode.js +0 -9
- package/lib/server.js +0 -155
- package/single.js +0 -3
package/esm/client.d.mts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { fromSecretKey } from "@ocap/wallet";
|
|
3
|
+
import axon from "axon";
|
|
4
|
+
|
|
5
|
+
//#region src/client.d.ts
|
|
6
|
+
interface ClientOptions {
|
|
7
|
+
port?: number;
|
|
8
|
+
hostname?: string;
|
|
9
|
+
autoConnect?: boolean;
|
|
10
|
+
wallet?: ReturnType<typeof fromSecretKey>;
|
|
11
|
+
did?: string;
|
|
12
|
+
sk?: string;
|
|
13
|
+
}
|
|
14
|
+
declare class Socket extends axon.SubEmitterSocket {
|
|
15
|
+
_authenticated: boolean;
|
|
16
|
+
_pendingMessages: [string, unknown][];
|
|
17
|
+
_wallet: ReturnType<typeof fromSecretKey>;
|
|
18
|
+
send: (event: string, data: unknown) => void;
|
|
19
|
+
constructor(opts: ClientOptions);
|
|
20
|
+
}
|
|
21
|
+
declare class Client extends EventEmitter {
|
|
22
|
+
opts: ClientOptions & {
|
|
23
|
+
wallet: ReturnType<typeof fromSecretKey>;
|
|
24
|
+
did: string;
|
|
25
|
+
};
|
|
26
|
+
_client: Socket;
|
|
27
|
+
_wallet: ReturnType<typeof fromSecretKey>;
|
|
28
|
+
constructor(opts?: ClientOptions);
|
|
29
|
+
connect(): void;
|
|
30
|
+
close(): void;
|
|
31
|
+
broadcast(event: string, data: unknown): void;
|
|
32
|
+
on(name: string, fn: (...args: unknown[]) => void): this;
|
|
33
|
+
off(name: string, fn?: (...args: unknown[]) => void): this;
|
|
34
|
+
/**
|
|
35
|
+
* Get the wallet instance
|
|
36
|
+
* Business can override wallet.signJWT() for remote signing
|
|
37
|
+
* @returns {WalletObject}
|
|
38
|
+
*/
|
|
39
|
+
getWallet(): ReturnType<typeof fromSecretKey>;
|
|
40
|
+
_bindEvent(): void;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { Client as default };
|
package/esm/client.mjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { EVENT_AUTH, EVENT_AUTH_FAIL, EVENT_AUTH_SUCCESS, RESERVED_EVENT_PREFIX } from "./constant.mjs";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import { DidType, isEthereumType, toTypeInfo } from "@arcblock/did";
|
|
4
|
+
import { WalletType, fromSecretKey } from "@ocap/wallet";
|
|
5
|
+
import axon from "axon";
|
|
6
|
+
import queue from "axon/lib/plugins/queue.js";
|
|
7
|
+
import roundrobin from "axon/lib/plugins/round-robin.js";
|
|
8
|
+
|
|
9
|
+
//#region src/client.ts
|
|
10
|
+
const checkEvent = (event) => {
|
|
11
|
+
if (event.startsWith(RESERVED_EVENT_PREFIX)) throw new Error(`event cannot start with ${RESERVED_EVENT_PREFIX}`);
|
|
12
|
+
};
|
|
13
|
+
const getWallet = (appSk, type) => {
|
|
14
|
+
let t = type;
|
|
15
|
+
let sk = appSk;
|
|
16
|
+
if (isEthereumType(DidType(type))) {
|
|
17
|
+
sk = appSk.slice(0, 66);
|
|
18
|
+
t = WalletType(type);
|
|
19
|
+
}
|
|
20
|
+
return fromSecretKey(sk, t);
|
|
21
|
+
};
|
|
22
|
+
const defaultOpts = {
|
|
23
|
+
port: void 0,
|
|
24
|
+
hostname: "0.0.0.0",
|
|
25
|
+
autoConnect: false,
|
|
26
|
+
wallet: void 0,
|
|
27
|
+
did: void 0,
|
|
28
|
+
sk: void 0
|
|
29
|
+
};
|
|
30
|
+
var Socket = class extends axon.SubEmitterSocket {
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
super();
|
|
33
|
+
const wallet = opts.wallet || getWallet(opts.sk, toTypeInfo(opts.did));
|
|
34
|
+
this._authenticated = false;
|
|
35
|
+
this._pendingMessages = [];
|
|
36
|
+
this._wallet = wallet;
|
|
37
|
+
this.sock.use((sock) => {
|
|
38
|
+
sock.on("connect", async () => {
|
|
39
|
+
this._authenticated = false;
|
|
40
|
+
const token = await wallet.signJWT();
|
|
41
|
+
sock.send(EVENT_AUTH, {
|
|
42
|
+
pk: wallet.publicKey,
|
|
43
|
+
token
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
sock.on("close", () => {
|
|
47
|
+
this._authenticated = false;
|
|
48
|
+
this._pendingMessages = [];
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
this.on(EVENT_AUTH_SUCCESS, () => {
|
|
52
|
+
this._authenticated = true;
|
|
53
|
+
const pending = this._pendingMessages.slice();
|
|
54
|
+
this._pendingMessages = [];
|
|
55
|
+
pending.forEach(([event, data]) => this.sock.send(event, data));
|
|
56
|
+
});
|
|
57
|
+
this.on(EVENT_AUTH_FAIL, ({ msg } = {}) => {
|
|
58
|
+
this.emit("error", { msg });
|
|
59
|
+
this._authenticated = false;
|
|
60
|
+
this._pendingMessages = [];
|
|
61
|
+
this.sock.socks.forEach((s) => s.close());
|
|
62
|
+
});
|
|
63
|
+
this.sock.use(queue());
|
|
64
|
+
this.sock.use(roundrobin({ fallback: this.sock.enqueue }));
|
|
65
|
+
const originalSend = this.sock.send.bind(this.sock);
|
|
66
|
+
this.send = (event, data) => {
|
|
67
|
+
const hasConnection = this.sock.socks && this.sock.socks.length > 0;
|
|
68
|
+
if (this._authenticated && hasConnection) originalSend(event, data);
|
|
69
|
+
else this._pendingMessages.push([event, data]);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var Client = class extends EventEmitter {
|
|
74
|
+
constructor(opts = {}) {
|
|
75
|
+
super();
|
|
76
|
+
if (!opts.port) throw new Error("port should not be empty");
|
|
77
|
+
if (!opts.wallet) {
|
|
78
|
+
if (!opts.did) throw new Error("did should not be empty when wallet is not provided");
|
|
79
|
+
if (!opts.sk) throw new Error("Either wallet or sk should be provided");
|
|
80
|
+
}
|
|
81
|
+
const wallet = opts.wallet || getWallet(opts.sk, toTypeInfo(opts.did));
|
|
82
|
+
const did = opts.wallet ? opts.wallet.address : opts.did;
|
|
83
|
+
if (wallet.address !== did) throw new Error("did does not match the provided wallet/sk");
|
|
84
|
+
this.opts = Object.assign({}, defaultOpts, opts, {
|
|
85
|
+
wallet,
|
|
86
|
+
did
|
|
87
|
+
});
|
|
88
|
+
this._client = new Socket(this.opts);
|
|
89
|
+
this._wallet = wallet;
|
|
90
|
+
this._bindEvent();
|
|
91
|
+
if (this.opts.autoConnect) this.connect();
|
|
92
|
+
}
|
|
93
|
+
connect() {
|
|
94
|
+
const { port, hostname } = this.opts;
|
|
95
|
+
this._client.connect(port, hostname);
|
|
96
|
+
}
|
|
97
|
+
close() {
|
|
98
|
+
this._client._authenticated = false;
|
|
99
|
+
this._client._pendingMessages = [];
|
|
100
|
+
this._client.close();
|
|
101
|
+
}
|
|
102
|
+
broadcast(event, data) {
|
|
103
|
+
checkEvent(event);
|
|
104
|
+
this._client.send(event, data);
|
|
105
|
+
}
|
|
106
|
+
on(name, fn) {
|
|
107
|
+
checkEvent(name);
|
|
108
|
+
super.on(name, fn);
|
|
109
|
+
this._client.on(name, fn);
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
off(name, fn) {
|
|
113
|
+
checkEvent(name);
|
|
114
|
+
this._client.off(name);
|
|
115
|
+
if (fn) super.off(name, fn);
|
|
116
|
+
else super.removeAllListeners(name);
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the wallet instance
|
|
121
|
+
* Business can override wallet.signJWT() for remote signing
|
|
122
|
+
* @returns {WalletObject}
|
|
123
|
+
*/
|
|
124
|
+
getWallet() {
|
|
125
|
+
return this._wallet;
|
|
126
|
+
}
|
|
127
|
+
_bindEvent() {
|
|
128
|
+
this._client.on("error", this.emit.bind(this, "error"));
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
var client_default = Client;
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
export { client_default as default };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/constant.d.ts
|
|
2
|
+
declare const RESERVED_EVENT_PREFIX = "EVENT_HUB:";
|
|
3
|
+
declare const EVENT_AUTH = "EVENT_HUB:AUTH";
|
|
4
|
+
declare const EVENT_AUTH_SUCCESS = "EVENT_HUB:AUTH_SUCCESS";
|
|
5
|
+
declare const EVENT_AUTH_FAIL = "EVENT_HUB:AUTH_FAIL";
|
|
6
|
+
//#endregion
|
|
7
|
+
export { EVENT_AUTH, EVENT_AUTH_FAIL, EVENT_AUTH_SUCCESS, RESERVED_EVENT_PREFIX };
|
package/esm/constant.mjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/constant.ts
|
|
2
|
+
const RESERVED_EVENT_PREFIX = "EVENT_HUB:";
|
|
3
|
+
const EVENT_AUTH = "EVENT_HUB:AUTH";
|
|
4
|
+
const EVENT_AUTH_SUCCESS = "EVENT_HUB:AUTH_SUCCESS";
|
|
5
|
+
const EVENT_AUTH_FAIL = "EVENT_HUB:AUTH_FAIL";
|
|
6
|
+
|
|
7
|
+
//#endregion
|
|
8
|
+
export { EVENT_AUTH, EVENT_AUTH_FAIL, EVENT_AUTH_SUCCESS, RESERVED_EVENT_PREFIX };
|
package/esm/index.d.mts
ADDED
package/esm/index.mjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import client_default from "./client.mjs";
|
|
2
|
+
import { Client } from "./single.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/index.ts
|
|
5
|
+
const port = Number(process.env.ABT_NODE_EVENT_PORT);
|
|
6
|
+
const hostname = process.env.ABT_NODE_EVENT_HOSTNAME || "127.0.0.1";
|
|
7
|
+
let did;
|
|
8
|
+
let sk;
|
|
9
|
+
if (process.env.BLOCKLET_APP_SK && process.env.BLOCKLET_APP_ID) {
|
|
10
|
+
sk = process.env.BLOCKLET_APP_SK;
|
|
11
|
+
did = process.env.BLOCKLET_APP_ID;
|
|
12
|
+
} else if (process.env.ABT_NODE_SK && process.env.ABT_NODE_DID) {
|
|
13
|
+
sk = process.env.ABT_NODE_SK;
|
|
14
|
+
did = process.env.ABT_NODE_DID;
|
|
15
|
+
}
|
|
16
|
+
let defaultExport;
|
|
17
|
+
if (port && did && sk) defaultExport = new client_default({
|
|
18
|
+
port,
|
|
19
|
+
hostname,
|
|
20
|
+
did,
|
|
21
|
+
sk,
|
|
22
|
+
autoConnect: true
|
|
23
|
+
});
|
|
24
|
+
else defaultExport = new Client();
|
|
25
|
+
var src_default = defaultExport;
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { client_default as Client, Client as ClientFallback, src_default as default };
|
package/esm/rpc.d.mts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import axon from "axon";
|
|
2
|
+
|
|
3
|
+
//#region src/rpc.d.ts
|
|
4
|
+
interface InflightEntry {
|
|
5
|
+
resolve: (value: unknown) => void;
|
|
6
|
+
reject: (reason: Error) => void;
|
|
7
|
+
to: ReturnType<typeof setTimeout>;
|
|
8
|
+
errorPrefix?: string;
|
|
9
|
+
}
|
|
10
|
+
interface RpcOptions {
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
respEvent?: string;
|
|
13
|
+
errorPrefix?: string;
|
|
14
|
+
}
|
|
15
|
+
interface SocketLike {
|
|
16
|
+
writable: boolean;
|
|
17
|
+
write(buf: Buffer): void;
|
|
18
|
+
}
|
|
19
|
+
declare class Client extends axon.Socket {
|
|
20
|
+
socks: SocketLike[];
|
|
21
|
+
port: number;
|
|
22
|
+
host: string;
|
|
23
|
+
online: boolean;
|
|
24
|
+
connectPromise: Promise<this> | null;
|
|
25
|
+
inflight: Map<string, InflightEntry>;
|
|
26
|
+
/**
|
|
27
|
+
* @param {number} port
|
|
28
|
+
* @param {string} host
|
|
29
|
+
*/
|
|
30
|
+
constructor(port: number, host?: string);
|
|
31
|
+
onmessage(): (buf: Buffer) => void;
|
|
32
|
+
sendEvent(event: string, data: unknown): void;
|
|
33
|
+
ensureOnline(): Promise<this>;
|
|
34
|
+
/**
|
|
35
|
+
* 通用 RPC:发送 event,等待响应(默认只按 reqId 匹配)
|
|
36
|
+
* @param {string} event 例如 'pm2/start'
|
|
37
|
+
* @param {object} payload 发送为 { reqId, payload }
|
|
38
|
+
* @param {object} [opts]
|
|
39
|
+
* @param {number} [opts.timeoutMs=120000]
|
|
40
|
+
* @param {string} [opts.respEvent] // 可选:仅用于调试;匹配靠 reqId,不强依赖此项
|
|
41
|
+
* @param {string} [opts.errorPrefix] // 统一错误前缀
|
|
42
|
+
* @returns {Promise<any>}
|
|
43
|
+
*/
|
|
44
|
+
rpc(event: string, payload?: unknown, {
|
|
45
|
+
timeoutMs,
|
|
46
|
+
respEvent,
|
|
47
|
+
errorPrefix
|
|
48
|
+
}?: RpcOptions): Promise<unknown>;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { Client as default };
|
package/esm/rpc.mjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import axon from "axon";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import Message from "amp-message";
|
|
4
|
+
|
|
5
|
+
//#region src/rpc.ts
|
|
6
|
+
var Client = class extends axon.Socket {
|
|
7
|
+
/**
|
|
8
|
+
* @param {number} port
|
|
9
|
+
* @param {string} host
|
|
10
|
+
*/
|
|
11
|
+
constructor(port, host = "127.0.0.1") {
|
|
12
|
+
super();
|
|
13
|
+
this.port = port;
|
|
14
|
+
this.host = host;
|
|
15
|
+
this.online = false;
|
|
16
|
+
this.connectPromise = null;
|
|
17
|
+
this.inflight = /* @__PURE__ */ new Map();
|
|
18
|
+
this.on("connect", () => {
|
|
19
|
+
this.online = true;
|
|
20
|
+
});
|
|
21
|
+
this.on("reconnect attempt", () => {
|
|
22
|
+
this.online = false;
|
|
23
|
+
});
|
|
24
|
+
this.on("disconnect", () => {
|
|
25
|
+
this.online = false;
|
|
26
|
+
});
|
|
27
|
+
this.on("error", () => {});
|
|
28
|
+
}
|
|
29
|
+
onmessage() {
|
|
30
|
+
return (buf) => {
|
|
31
|
+
try {
|
|
32
|
+
const [event, data] = new Message(buf).args;
|
|
33
|
+
if (data && typeof data === "object" && data.reqId) {
|
|
34
|
+
const entry = this.inflight.get(data.reqId);
|
|
35
|
+
if (entry) {
|
|
36
|
+
clearTimeout(entry.to);
|
|
37
|
+
this.inflight.delete(data.reqId);
|
|
38
|
+
if (data.ok === false) entry.reject(new Error(entry.errorPrefix ? `${entry.errorPrefix}: ${data.error}` : data.error || "request failed"));
|
|
39
|
+
else entry.resolve(data.data);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
this.emit(event, data);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error("[axon] decode error", e);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
sendEvent(event, data) {
|
|
50
|
+
const buf = this.pack([event, data]);
|
|
51
|
+
const s = this.socks[0];
|
|
52
|
+
if (s?.writable) s.write(buf);
|
|
53
|
+
else throw new Error("axon socket not writable/connected");
|
|
54
|
+
}
|
|
55
|
+
async ensureOnline() {
|
|
56
|
+
if (!this.port || !this.host) throw new Error("PORT and HOST must be set via constructor: new Client(port, host)");
|
|
57
|
+
if (!this.connectPromise) this.connectPromise = new Promise((resolve, reject) => {
|
|
58
|
+
this.connect(this.port, this.host, (err) => err ? reject(err) : resolve(this));
|
|
59
|
+
});
|
|
60
|
+
await this.connectPromise;
|
|
61
|
+
if (this.online) return this;
|
|
62
|
+
await new Promise((r) => this.once("connect", r));
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 通用 RPC:发送 event,等待响应(默认只按 reqId 匹配)
|
|
67
|
+
* @param {string} event 例如 'pm2/start'
|
|
68
|
+
* @param {object} payload 发送为 { reqId, payload }
|
|
69
|
+
* @param {object} [opts]
|
|
70
|
+
* @param {number} [opts.timeoutMs=120000]
|
|
71
|
+
* @param {string} [opts.respEvent] // 可选:仅用于调试;匹配靠 reqId,不强依赖此项
|
|
72
|
+
* @param {string} [opts.errorPrefix] // 统一错误前缀
|
|
73
|
+
* @returns {Promise<any>}
|
|
74
|
+
*/
|
|
75
|
+
async rpc(event, payload = {}, { timeoutMs = 12e4, respEvent, errorPrefix } = {}) {
|
|
76
|
+
await this.ensureOnline();
|
|
77
|
+
const reqId = randomUUID();
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
const to = setTimeout(() => {
|
|
80
|
+
this.inflight.delete(reqId);
|
|
81
|
+
reject(/* @__PURE__ */ new Error(`${errorPrefix || "request"} timeout`));
|
|
82
|
+
}, timeoutMs);
|
|
83
|
+
this.inflight.set(reqId, {
|
|
84
|
+
resolve,
|
|
85
|
+
reject,
|
|
86
|
+
to,
|
|
87
|
+
errorPrefix
|
|
88
|
+
});
|
|
89
|
+
this.sendEvent(event, {
|
|
90
|
+
reqId,
|
|
91
|
+
payload
|
|
92
|
+
});
|
|
93
|
+
if (respEvent) {
|
|
94
|
+
const once = (data) => {
|
|
95
|
+
if (!data || data.reqId !== reqId) return;
|
|
96
|
+
this.off(respEvent, once);
|
|
97
|
+
};
|
|
98
|
+
this.on(respEvent, once);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var rpc_default = Client;
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
export { rpc_default as default };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import server_default from "./server.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/server-abtnode.ts
|
|
4
|
+
const port = Number(process.env.ABT_NODE_EVENT_PORT);
|
|
5
|
+
const hostname = Number(process.env.ABT_NODE_EVENT_HOSTNAME) || "127.0.0.1";
|
|
6
|
+
const server = new server_default();
|
|
7
|
+
server.bind(port, hostname);
|
|
8
|
+
var server_abtnode_default = server;
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
export { server_abtnode_default as default };
|
package/esm/server.d.mts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import axon from "axon";
|
|
2
|
+
|
|
3
|
+
//#region src/server.d.ts
|
|
4
|
+
interface JwtLike {
|
|
5
|
+
verify(token: string, pk: string): Promise<boolean>;
|
|
6
|
+
decode(token: string): {
|
|
7
|
+
iss: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
interface ServerOptions {
|
|
11
|
+
jwt?: JwtLike;
|
|
12
|
+
}
|
|
13
|
+
interface SocketWithChannel {
|
|
14
|
+
channel?: string;
|
|
15
|
+
writable: boolean;
|
|
16
|
+
write(buf: Buffer): void;
|
|
17
|
+
_peername?: {
|
|
18
|
+
family: string;
|
|
19
|
+
address: string;
|
|
20
|
+
port: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
interface RouteHandler {
|
|
24
|
+
fn: (payload: unknown, ctx: {
|
|
25
|
+
sock: SocketWithChannel;
|
|
26
|
+
channel?: string;
|
|
27
|
+
event: string;
|
|
28
|
+
server: Server;
|
|
29
|
+
}) => unknown | Promise<unknown>;
|
|
30
|
+
authRequired: boolean;
|
|
31
|
+
}
|
|
32
|
+
declare class Server extends axon.Socket {
|
|
33
|
+
jwt: JwtLike;
|
|
34
|
+
handlers: Map<string, RouteHandler>;
|
|
35
|
+
socks: SocketWithChannel[];
|
|
36
|
+
constructor(opts?: ServerOptions);
|
|
37
|
+
onmessage(sock: SocketWithChannel): (buf: Buffer) => void;
|
|
38
|
+
send(event: string, data: unknown, channel: string): this;
|
|
39
|
+
/**
|
|
40
|
+
* 注册路由
|
|
41
|
+
* @param {string} event
|
|
42
|
+
* @param {(payload:any, ctx:{sock:any, channel?:string, event:string, server:Server})=>any|Promise<any>} handler
|
|
43
|
+
* @param {{authRequired?: boolean}} [opts] - 默认 true;设为 false 则该事件免鉴权
|
|
44
|
+
*/
|
|
45
|
+
register(event: string, handler: (payload: unknown, ctx: {
|
|
46
|
+
sock: SocketWithChannel;
|
|
47
|
+
channel?: string;
|
|
48
|
+
event: string;
|
|
49
|
+
server: Server;
|
|
50
|
+
}) => unknown | Promise<unknown>, opts?: {
|
|
51
|
+
authRequired?: boolean;
|
|
52
|
+
}): this;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { Server as default };
|
package/esm/server.mjs
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { EVENT_AUTH, EVENT_AUTH_FAIL, EVENT_AUTH_SUCCESS } from "./constant.mjs";
|
|
2
|
+
import axon from "axon";
|
|
3
|
+
import Message from "amp-message";
|
|
4
|
+
import * as JWT from "@arcblock/jwt";
|
|
5
|
+
|
|
6
|
+
//#region src/server.ts
|
|
7
|
+
const getDid = (jwt) => jwt.iss.replace(/^did:abt:/, "");
|
|
8
|
+
function normalizeReq(input) {
|
|
9
|
+
if (input && typeof input === "object" && ("payload" in input || "reqId" in input)) {
|
|
10
|
+
const { reqId, payload } = input;
|
|
11
|
+
return {
|
|
12
|
+
reqId,
|
|
13
|
+
payload
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
reqId: void 0,
|
|
18
|
+
payload: input
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function normalizeRes(result) {
|
|
22
|
+
if (result && typeof result === "object" && ("ok" in result || "data" in result || "error" in result)) return result;
|
|
23
|
+
return {
|
|
24
|
+
ok: true,
|
|
25
|
+
data: result
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
var Server = class extends axon.Socket {
|
|
29
|
+
constructor(opts = {}) {
|
|
30
|
+
super();
|
|
31
|
+
this.jwt = opts?.jwt || JWT;
|
|
32
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
33
|
+
this.on("message", async (event, data, sock) => {
|
|
34
|
+
const route = this.handlers.get(event);
|
|
35
|
+
if (route) {
|
|
36
|
+
const { fn, authRequired } = route;
|
|
37
|
+
if (authRequired && !sock.channel) {
|
|
38
|
+
console.error(`unauthenticated socket blocked for event "${event}"`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const { reqId, payload } = normalizeReq(data);
|
|
42
|
+
try {
|
|
43
|
+
const res = normalizeRes(await fn(payload, {
|
|
44
|
+
sock,
|
|
45
|
+
channel: sock.channel,
|
|
46
|
+
event,
|
|
47
|
+
server: this
|
|
48
|
+
}));
|
|
49
|
+
const replyEvent = `${event}:res`;
|
|
50
|
+
if (sock.channel) this.send(replyEvent, {
|
|
51
|
+
reqId,
|
|
52
|
+
...res
|
|
53
|
+
}, sock.channel);
|
|
54
|
+
else {
|
|
55
|
+
const buf = this.pack([replyEvent, {
|
|
56
|
+
reqId,
|
|
57
|
+
...res
|
|
58
|
+
}]);
|
|
59
|
+
if (sock.writable) sock.write(buf);
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
const replyEvent = `${event}:res`;
|
|
63
|
+
const payloadErr = {
|
|
64
|
+
reqId,
|
|
65
|
+
ok: false,
|
|
66
|
+
error: err?.message || String(err)
|
|
67
|
+
};
|
|
68
|
+
if (sock.channel) this.send(replyEvent, payloadErr, sock.channel);
|
|
69
|
+
else {
|
|
70
|
+
const buf = this.pack([replyEvent, payloadErr]);
|
|
71
|
+
if (sock.writable) sock.write(buf);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!sock.channel) {
|
|
77
|
+
console.error("skip message of unauthenticated socket");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.send(event, data, sock.channel);
|
|
81
|
+
});
|
|
82
|
+
this.on("connect", (socket) => {
|
|
83
|
+
console.log("event-hub client connected", socket._peername);
|
|
84
|
+
});
|
|
85
|
+
this.on("reconnect attempt", (socket) => {
|
|
86
|
+
console.log("event-hub client reconnect", socket._peername);
|
|
87
|
+
});
|
|
88
|
+
this.on("disconnect", (socket) => {
|
|
89
|
+
console.log("event-hub client disconnected", socket._peername);
|
|
90
|
+
});
|
|
91
|
+
this.on("drop", (args) => {
|
|
92
|
+
console.log("event-hub server drop message", args);
|
|
93
|
+
});
|
|
94
|
+
this.on("error", (err) => {
|
|
95
|
+
console.log("event-hub server error", err);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
onmessage(sock) {
|
|
99
|
+
return (buf) => {
|
|
100
|
+
const [event, data] = new Message(buf).args;
|
|
101
|
+
if (event === EVENT_AUTH) {
|
|
102
|
+
const { pk, token } = data;
|
|
103
|
+
this.jwt.verify(token, pk).then((valid) => {
|
|
104
|
+
if (!valid) throw new Error("token verify failed");
|
|
105
|
+
sock.channel = getDid(this.jwt.decode(token));
|
|
106
|
+
const successBuf = this.pack([EVENT_AUTH_SUCCESS, {}]);
|
|
107
|
+
sock.write(successBuf);
|
|
108
|
+
}).catch((err) => {
|
|
109
|
+
console.error(err);
|
|
110
|
+
const resBuf = this.pack([EVENT_AUTH_FAIL, { msg: err.message }]);
|
|
111
|
+
sock.write(resBuf);
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
this.emit("message", event, data, sock);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
send(event, data, channel) {
|
|
119
|
+
const { socks } = this;
|
|
120
|
+
const buf = this.pack([event, data]);
|
|
121
|
+
for (const sock of socks) if (sock.channel === channel && sock.writable) sock.write(buf);
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 注册路由
|
|
126
|
+
* @param {string} event
|
|
127
|
+
* @param {(payload:any, ctx:{sock:any, channel?:string, event:string, server:Server})=>any|Promise<any>} handler
|
|
128
|
+
* @param {{authRequired?: boolean}} [opts] - 默认 true;设为 false 则该事件免鉴权
|
|
129
|
+
*/
|
|
130
|
+
register(event, handler, opts = {}) {
|
|
131
|
+
const authRequired = opts.authRequired !== false;
|
|
132
|
+
this.handlers.set(event, {
|
|
133
|
+
fn: handler,
|
|
134
|
+
authRequired
|
|
135
|
+
});
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var server_default = Server;
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
export { server_default as default };
|
package/esm/single.d.mts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
|
|
3
|
+
//#region src/single.d.ts
|
|
4
|
+
declare class Client extends EventEmitter {
|
|
5
|
+
broadcast(...args: Parameters<EventEmitter['emit']>): boolean;
|
|
6
|
+
off(event: string | symbol): this;
|
|
7
|
+
}
|
|
8
|
+
declare const _default: Client;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { Client, _default as default };
|
package/esm/single.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
|
|
3
|
+
//#region src/single.ts
|
|
4
|
+
var Client = class extends EventEmitter {
|
|
5
|
+
broadcast(...args) {
|
|
6
|
+
return this.emit(...args);
|
|
7
|
+
}
|
|
8
|
+
off(event) {
|
|
9
|
+
this.removeAllListeners(event);
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var single_default = new Client();
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { Client, single_default as default };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
|
|
29
|
+
exports.__toESM = __toESM;
|