@zimi/remote 0.2.0 → 0.2.1-alpha.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
@@ -3,6 +3,8 @@
3
3
  - 本地可以是浏览器、服务器,甚至一些受限的 `js` 子集
4
4
  - 远端可以是任何终端,如 `iframe` / `Java` 服务器 等
5
5
  - 对远端响应的数据格式也不严格限制(可以集中解析)
6
+ - 已在公司游戏前后端通信中应用,极大地降低了通信成本(简化调用)
7
+ - ts 类型严格
6
8
 
7
9
  ## install
8
10
  ```
@@ -11,7 +13,7 @@ pnpm i @zimi/remote
11
13
 
12
14
  ## examples
13
15
 
14
- ### 使用示例
16
+ ### 调用示意
15
17
 
16
18
  ```ts
17
19
 
@@ -0,0 +1,32 @@
1
+ export interface AdaptorPackageData {
2
+ /**
3
+ * 自身设备 id,应确保唯一性(对方能凭借该 deviceId 找到该设备)
4
+ */
5
+ deviceId: string;
6
+ /**
7
+ * 对方的设备 id
8
+ */
9
+ targetDeviceId: string;
10
+ /**
11
+ * 远程调用的对方的方法名
12
+ */
13
+ name: string;
14
+ data: unknown;
15
+ /**
16
+ * 所需回调的方法名(如果需要回调的话)
17
+ */
18
+ callbackName?: string;
19
+ }
20
+ type Func<D = unknown, R = unknown> = (data: D) => R;
21
+ export type AdaptorCallback = Func<AdaptorPackageData, void>;
22
+ export interface Adaptor {
23
+ every: (callback: AdaptorCallback) => void;
24
+ /**
25
+ * off 用于移除 once 注册的事件,当事件超时后,需要主动 off
26
+ */
27
+ off: (name: string, callback: AdaptorCallback) => void;
28
+ on: (name: string, callback: AdaptorCallback) => void;
29
+ once: (name: string, callback: AdaptorCallback) => void;
30
+ emit: (data: AdaptorPackageData) => void;
31
+ }
32
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=adaptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptor.js","sourceRoot":"","sources":["../src/adaptor.ts"],"names":[],"mappings":""}
@@ -0,0 +1,37 @@
1
+ import EventEmitter from 'eventemitter3';
2
+ import { type AdaptorPackageData } from '../../adaptor';
3
+ interface RemoteChannel {
4
+ onClientEvent(fn: (e: unknown) => void): void;
5
+ }
6
+ /**
7
+ * 这是 [dao3 游戏](https://dao3.fun/) 代码客户端适配器。
8
+ * ```ts
9
+ * const remoteManager = new ClientSideRemoteChannelEventManager(remoteChannel)
10
+ *
11
+ * export const remoteAdaptor = {
12
+ * every: remoteManager.onEvery.bind(remoteManager),
13
+ * off: remoteManager.off.bind(remoteManager),
14
+ * on: remoteManager.on.bind(remoteManager),
15
+ * once: remoteManager.once.bind(remoteManager),
16
+ * emit: (e) => {
17
+ * remoteChannel.sendServerEvent(e)
18
+ * },
19
+ * } satisfies Adaptor
20
+ *
21
+ * export const remote = new Remote<RemoteFuncsFromClient, RemoteFuncsFromServer>(
22
+ * remoteAdaptor,
23
+ * {
24
+ * deviceId: `client-${Math.random().toString(36).slice(2)}`,
25
+ * }
26
+ * )
27
+ *
28
+ * void remote._.initConnection()
29
+ * ```
30
+ */
31
+ export declare class ClientSideRemoteChannelEventManager extends EventEmitter<{
32
+ [key: string]: [AdaptorPackageData];
33
+ }> {
34
+ constructor(remoteChannel: RemoteChannel);
35
+ onEvery(fn: (args: AdaptorPackageData) => void): void;
36
+ }
37
+ export {};
@@ -0,0 +1,45 @@
1
+ import EventEmitter from 'eventemitter3';
2
+ import { isRemoteAdaptorData } from '../../remote';
3
+ /**
4
+ * 这是 [dao3 游戏](https://dao3.fun/) 代码客户端适配器。
5
+ * ```ts
6
+ * const remoteManager = new ClientSideRemoteChannelEventManager(remoteChannel)
7
+ *
8
+ * export const remoteAdaptor = {
9
+ * every: remoteManager.onEvery.bind(remoteManager),
10
+ * off: remoteManager.off.bind(remoteManager),
11
+ * on: remoteManager.on.bind(remoteManager),
12
+ * once: remoteManager.once.bind(remoteManager),
13
+ * emit: (e) => {
14
+ * remoteChannel.sendServerEvent(e)
15
+ * },
16
+ * } satisfies Adaptor
17
+ *
18
+ * export const remote = new Remote<RemoteFuncsFromClient, RemoteFuncsFromServer>(
19
+ * remoteAdaptor,
20
+ * {
21
+ * deviceId: `client-${Math.random().toString(36).slice(2)}`,
22
+ * }
23
+ * )
24
+ *
25
+ * void remote._.initConnection()
26
+ * ```
27
+ */
28
+ export class ClientSideRemoteChannelEventManager extends EventEmitter {
29
+ constructor(remoteChannel) {
30
+ super();
31
+ remoteChannel.onClientEvent((e) => {
32
+ if (!isRemoteAdaptorData(e)) {
33
+ return;
34
+ }
35
+ if (!e.name.startsWith('__REMOTE_VALUE_REQ__')) {
36
+ this.emit('__remote_every__', e);
37
+ }
38
+ this.emit(e.name, e);
39
+ });
40
+ }
41
+ onEvery(fn) {
42
+ this.on('__remote_every__', fn);
43
+ }
44
+ }
45
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/adaptors/dao3/client.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAOlD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,OAAO,mCAAoC,SAAQ,YAEvD;IACA,YAAY,aAA4B;QACtC,KAAK,EAAE,CAAA;QACP,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5B,OAAM;YACR,CAAC;YACD,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,sBAAsB,CAAC,EAAE,CAAC;gBAC/C,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAA;YAClC,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QACtB,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,CAAC,EAAsC;QAC5C,IAAI,CAAC,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAA;IACjC,CAAC;CACF"}
@@ -0,0 +1,80 @@
1
+ import EventEmitter from 'eventemitter3';
2
+ import { type AdaptorPackageData } from '../../adaptor';
3
+ interface GamePlayerEntityLike {
4
+ player: {
5
+ userKey: string;
6
+ };
7
+ }
8
+ interface RemoteChannel<T extends GamePlayerEntityLike> {
9
+ onServerEvent(fn: (e: {
10
+ args: unknown;
11
+ entity: T;
12
+ }) => void): void;
13
+ }
14
+ /**
15
+ * 这是 [dao3 游戏](https://dao3.fun/) 代码服务端适配器。
16
+ * ```ts
17
+ * const remoteManager = new ServerSideRemoteChannelEventManager(remoteChannel)
18
+ *
19
+ * world.onPlayerLeave(
20
+ * remoteManager.onPlayerLeave.bind(remoteManager)
21
+ * )
22
+ *
23
+ * export const remoteAdaptor = {
24
+ * every: remoteManager.onEvery.bind(remoteManager),
25
+ * off: remoteManager.off.bind(remoteManager),
26
+ * on: remoteManager.on.bind(remoteManager),
27
+ * once: remoteManager.once.bind(remoteManager),
28
+ * emit: (e) => {
29
+ * const entity = remoteManager.getEntity(e.targetDeviceId)
30
+ * if (!entity) {
31
+ * console.error('entity not found')
32
+ * return
33
+ * }
34
+ * remoteChannel.sendClientEvent(entity, e as unknown as JSONValue)
35
+ * },
36
+ * } satisfies Adaptor
37
+ *
38
+ * export const remote = new Remote<RemoteFuncsFromServer, RemoteFuncsFromClient>(
39
+ * remoteAdaptor,
40
+ * {
41
+ * deviceId: 'server',
42
+ * }
43
+ * )
44
+ *
45
+ * remote.register('initConnection', async () => {
46
+ * // 什么也不干,单纯的让客户端在服务端“备案”
47
+ * })
48
+ *
49
+ * export const getEntity = remoteManager.getEntity.bind(remoteManager)
50
+ * export const getIdByEntity = remoteManager.getIdByEntity.bind(remoteManager)
51
+ * export const waitForRegister = remoteManager.waitForRegister.bind(remoteManager)
52
+ * export const remoteTo = remoteManager.remoteTo.bind(remoteManager)
53
+ * ```
54
+ */
55
+ export declare class ServerSideRemoteChannelEventManager<T extends GamePlayerEntityLike> extends EventEmitter<{
56
+ [key: string]: [AdaptorPackageData];
57
+ }> {
58
+ entityMap: [string, T][];
59
+ constructor(remoteChannel: RemoteChannel<T>);
60
+ onPlayerLeave(e: {
61
+ entity: T;
62
+ }): void;
63
+ onEvery(fn: (args: AdaptorPackageData) => void): void;
64
+ waitForRegister(entity: T): Promise<void>;
65
+ getEntity(deviceId: string): T | undefined;
66
+ getIdByEntity(entity: T): string | undefined;
67
+ remoteTo(config: {
68
+ target: T;
69
+ }): {
70
+ targetDeviceId: string;
71
+ };
72
+ remoteTo(config: {
73
+ target: T;
74
+ timeoutMs: number;
75
+ }): {
76
+ targetDeviceId: string;
77
+ timeoutMs: number;
78
+ };
79
+ }
80
+ export {};
@@ -0,0 +1,105 @@
1
+ import EventEmitter from 'eventemitter3';
2
+ import { isRemoteAdaptorData } from '../../remote';
3
+ import { isRemoteValueEvent } from '../../remoteValue/exposeToRemote';
4
+ /**
5
+ * 这是 [dao3 游戏](https://dao3.fun/) 代码服务端适配器。
6
+ * ```ts
7
+ * const remoteManager = new ServerSideRemoteChannelEventManager(remoteChannel)
8
+ *
9
+ * world.onPlayerLeave(
10
+ * remoteManager.onPlayerLeave.bind(remoteManager)
11
+ * )
12
+ *
13
+ * export const remoteAdaptor = {
14
+ * every: remoteManager.onEvery.bind(remoteManager),
15
+ * off: remoteManager.off.bind(remoteManager),
16
+ * on: remoteManager.on.bind(remoteManager),
17
+ * once: remoteManager.once.bind(remoteManager),
18
+ * emit: (e) => {
19
+ * const entity = remoteManager.getEntity(e.targetDeviceId)
20
+ * if (!entity) {
21
+ * console.error('entity not found')
22
+ * return
23
+ * }
24
+ * remoteChannel.sendClientEvent(entity, e as unknown as JSONValue)
25
+ * },
26
+ * } satisfies Adaptor
27
+ *
28
+ * export const remote = new Remote<RemoteFuncsFromServer, RemoteFuncsFromClient>(
29
+ * remoteAdaptor,
30
+ * {
31
+ * deviceId: 'server',
32
+ * }
33
+ * )
34
+ *
35
+ * remote.register('initConnection', async () => {
36
+ * // 什么也不干,单纯的让客户端在服务端“备案”
37
+ * })
38
+ *
39
+ * export const getEntity = remoteManager.getEntity.bind(remoteManager)
40
+ * export const getIdByEntity = remoteManager.getIdByEntity.bind(remoteManager)
41
+ * export const waitForRegister = remoteManager.waitForRegister.bind(remoteManager)
42
+ * export const remoteTo = remoteManager.remoteTo.bind(remoteManager)
43
+ * ```
44
+ */
45
+ export class ServerSideRemoteChannelEventManager extends EventEmitter {
46
+ constructor(remoteChannel) {
47
+ super();
48
+ Object.defineProperty(this, "entityMap", {
49
+ enumerable: true,
50
+ configurable: true,
51
+ writable: true,
52
+ value: []
53
+ });
54
+ remoteChannel.onServerEvent((e) => {
55
+ const { args } = e;
56
+ if (!isRemoteAdaptorData(args)) {
57
+ return;
58
+ }
59
+ const prevItem = this.entityMap.find((item) => item[1].player.userKey === e.entity.player.userKey);
60
+ if (prevItem) {
61
+ prevItem[0] = args.deviceId;
62
+ prevItem[1] = e.entity;
63
+ }
64
+ else {
65
+ this.entityMap.push([args.deviceId, e.entity]);
66
+ }
67
+ if (!isRemoteValueEvent(args.name)) {
68
+ this.emit('__remote_every__', args);
69
+ }
70
+ this.emit(args.name, args);
71
+ });
72
+ }
73
+ onPlayerLeave(e) {
74
+ this.entityMap = this.entityMap.filter((item) => item[1].player.userKey !== e.entity.player.userKey);
75
+ }
76
+ onEvery(fn) {
77
+ this.on('__remote_every__', fn);
78
+ }
79
+ waitForRegister(entity) {
80
+ return new Promise((resolve) => {
81
+ if (this.entityMap.some(([_, en]) => en.player.userKey === entity.player.userKey)) {
82
+ resolve();
83
+ return;
84
+ }
85
+ this.once('__remote_every__', () => {
86
+ resolve();
87
+ });
88
+ });
89
+ }
90
+ getEntity(deviceId) {
91
+ var _a;
92
+ return (_a = this.entityMap.find((item) => item[0] === deviceId)) === null || _a === void 0 ? void 0 : _a[1];
93
+ }
94
+ getIdByEntity(entity) {
95
+ var _a;
96
+ return (_a = this.entityMap.find((item) => item[1].player.userKey === entity.player.userKey)) === null || _a === void 0 ? void 0 : _a[0];
97
+ }
98
+ remoteTo({ target, ...rest }) {
99
+ return {
100
+ targetDeviceId: this.getIdByEntity(target),
101
+ ...rest,
102
+ };
103
+ }
104
+ }
105
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/adaptors/dao3/server.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,7 @@ export const remoteEventManager = new RemoteEventManager();
17
17
  export function createHttpAdaptor({ onEmit, }) {
18
18
  const adaptor = {
19
19
  every: remoteEventManager.onEvery.bind(remoteEventManager),
20
+ on: remoteEventManager.on.bind(remoteEventManager),
20
21
  once: remoteEventManager.once.bind(remoteEventManager),
21
22
  off: remoteEventManager.off.bind(remoteEventManager),
22
23
  emit: onEmit,
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/adaptors/http.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AAIxC,MAAM,kBAAmB,SAAQ,YAE/B;IAFF;;QAGE;;;;mBAAmB,kBAAkB;WAAA;IAKvC,CAAC;IAHC,OAAO,CAAC,EAAsC;QAC5C,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAA;IACpC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,kBAAkB,EAAE,CAAA;AAE1D,MAAM,UAAU,iBAAiB,CAAC,EAChC,MAAM,GAGP;IACC,MAAM,OAAO,GAAY;QACvB,KAAK,EAAE,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC;QAC1D,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC;QACtD,GAAG,EAAE,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC;QACpD,IAAI,EAAE,MAAM;KACb,CAAA;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/adaptors/http.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AAIxC,MAAM,kBAAmB,SAAQ,YAE/B;IAFF;;QAGE;;;;mBAAmB,kBAAkB;WAAA;IAKvC,CAAC;IAHC,OAAO,CAAC,EAAsC;QAC5C,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAA;IACpC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,kBAAkB,EAAE,CAAA;AAE1D,MAAM,UAAU,iBAAiB,CAAC,EAChC,MAAM,GAGP;IACC,MAAM,OAAO,GAAY;QACvB,KAAK,EAAE,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC;QAC1D,EAAE,EAAE,kBAAkB,CAAC,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC;QAClD,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC;QACtD,GAAG,EAAE,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC;QACpD,IAAI,EAAE,MAAM;KACb,CAAA;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -29,6 +29,7 @@ export function createIframeAdaptor({ onEmit, }) {
29
29
  const remoteEventManager = new RemoteEventManager();
30
30
  const adaptor = {
31
31
  every: remoteEventManager.onEvery.bind(remoteEventManager),
32
+ on: remoteEventManager.on.bind(remoteEventManager),
32
33
  once: remoteEventManager.once.bind(remoteEventManager),
33
34
  off: remoteEventManager.off.bind(remoteEventManager),
34
35
  emit: onEmit,
@@ -1 +1 @@
1
- {"version":3,"file":"iframe.js","sourceRoot":"","sources":["../../src/adaptors/iframe.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAI/C,MAAM,kBAAmB,SAAQ,YAE/B;IAGA;QACE,KAAK,EAAE,CAAA;QAHT;;;;mBAAmB,kBAAkB;WAAA;QAInC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,OAAM;QACR,CAAC;QACD,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YAC3C,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAA;YACtB,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;gBAC1B,oCAAoC;gBACpC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAA;YACxC,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,CAAC,EAAsC;QAC5C,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAA;IACpC,CAAC;CACF;AAED,MAAM,UAAU,mBAAmB,CAAC,EAClC,MAAM,GAGP;IACC,MAAM,kBAAkB,GAAG,IAAI,kBAAkB,EAAE,CAAA;IAEnD,MAAM,OAAO,GAAY;QACvB,KAAK,EAAE,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC;QAC1D,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC;QACtD,GAAG,EAAE,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC;QACpD,IAAI,EAAE,MAAM;KACb,CAAA;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
1
+ {"version":3,"file":"iframe.js","sourceRoot":"","sources":["../../src/adaptors/iframe.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAI/C,MAAM,kBAAmB,SAAQ,YAE/B;IAGA;QACE,KAAK,EAAE,CAAA;QAHT;;;;mBAAmB,kBAAkB;WAAA;QAInC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,OAAM;QACR,CAAC;QACD,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YAC3C,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAA;YACtB,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;gBAC1B,oCAAoC;gBACpC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAA;YACxC,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,CAAC,EAAsC;QAC5C,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAA;IACpC,CAAC;CACF;AAED,MAAM,UAAU,mBAAmB,CAAC,EAClC,MAAM,GAGP;IACC,MAAM,kBAAkB,GAAG,IAAI,kBAAkB,EAAE,CAAA;IAEnD,MAAM,OAAO,GAAY;QACvB,KAAK,EAAE,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC;QAC1D,EAAE,EAAE,kBAAkB,CAAC,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC;QAClD,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC;QACtD,GAAG,EAAE,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC;QACpD,IAAI,EAAE,MAAM;KACb,CAAA;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,10 @@
1
- export { Remote, isRemoteAdaptorData } from './remote';
2
- export type { AdaptorPackageData, Adaptor } from './adaptor';
3
- export { RemoteError, RemoteNotFoundError, RemoteTimeoutError, response, } from './response';
4
- export { createIframeAdaptor } from './adaptors/iframe';
5
- export { createHttpAdaptor, remoteEventManager } from './adaptors/http';
1
+ export * from './remote';
2
+ export * from './adaptor';
3
+ export * from './response';
4
+ export * from './remoteValue/remoteValue';
5
+ export * from './remoteValue/exposeToRemote';
6
+ export type { ToFunc } from './remoteValue/type';
7
+ export * from './adaptors/iframe';
8
+ export * from './adaptors/http';
9
+ export * from './adaptors/dao3/client';
10
+ export * from './adaptors/dao3/server';
package/dist/index.js CHANGED
@@ -1,5 +1,10 @@
1
- export { Remote, isRemoteAdaptorData } from './remote';
2
- export { RemoteError, RemoteNotFoundError, RemoteTimeoutError, response, } from './response';
3
- export { createIframeAdaptor } from './adaptors/iframe';
4
- export { createHttpAdaptor, remoteEventManager } from './adaptors/http';
1
+ export * from './remote';
2
+ export * from './adaptor';
3
+ export * from './response';
4
+ export * from './remoteValue/remoteValue';
5
+ export * from './remoteValue/exposeToRemote';
6
+ export * from './adaptors/iframe';
7
+ export * from './adaptors/http';
8
+ export * from './adaptors/dao3/client';
9
+ export * from './adaptors/dao3/server';
5
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAEtD,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,QAAQ,GACT,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAE1B,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAG5C,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA"}
@@ -0,0 +1,16 @@
1
+ import { Adaptor, AdaptorPackageData } from '../adaptor';
2
+ interface ExposeProps {
3
+ globalName: string;
4
+ adaptor: Adaptor;
5
+ /**
6
+ * ['*'] or ['device_id_1', 'device_id_2']
7
+ */
8
+ exposeTo: string[];
9
+ /**
10
+ * 你可以在该回调中抛错,以阻止远程调用
11
+ */
12
+ onRequest?: (e: AdaptorPackageData) => void | Promise<void>;
13
+ }
14
+ export declare function isRemoteValueEvent(eventName: string): boolean;
15
+ export declare function exposeToRemote<T extends object>(obj: T, options: ExposeProps): () => void;
16
+ export {};
@@ -0,0 +1,50 @@
1
+ import { RemoteError, response } from '../response';
2
+ function defaultOnRequest(e) {
3
+ return e;
4
+ }
5
+ export function isRemoteValueEvent(eventName) {
6
+ return eventName.startsWith('__REMOTE_VALUE_REQ__');
7
+ }
8
+ export function exposeToRemote(obj, options) {
9
+ const { globalName, adaptor, exposeTo, onRequest = defaultOnRequest, } = options;
10
+ const callback = async (e) => {
11
+ var _a, _b;
12
+ try {
13
+ await onRequest(e);
14
+ if (!exposeTo.includes(e.deviceId) && !exposeTo.includes('*')) {
15
+ throw new RemoteError('permission denied');
16
+ }
17
+ const { paths, args } = e.data;
18
+ let target = obj;
19
+ for (let i = 0; i < paths.length; i += 1) {
20
+ target = target[paths[i]];
21
+ }
22
+ let res;
23
+ if (target instanceof Function) {
24
+ res = await target(...args);
25
+ }
26
+ else {
27
+ res = target;
28
+ }
29
+ adaptor.emit({
30
+ name: (_a = e.callbackName) !== null && _a !== void 0 ? _a : 'IMPOSSIBLE_NO_CALLBACK_NAME',
31
+ deviceId: e.targetDeviceId,
32
+ targetDeviceId: e.deviceId,
33
+ data: response.success(res),
34
+ });
35
+ }
36
+ catch (error) {
37
+ adaptor.emit({
38
+ name: (_b = e.callbackName) !== null && _b !== void 0 ? _b : 'IMPOSSIBLE_NO_CALLBACK_NAME',
39
+ deviceId: e.targetDeviceId,
40
+ targetDeviceId: e.deviceId,
41
+ data: response.error(RemoteError.fromError(error)),
42
+ });
43
+ }
44
+ };
45
+ adaptor.on(`__REMOTE_VALUE_REQ__${globalName}`, callback);
46
+ return () => {
47
+ adaptor.off(`__REMOTE_VALUE_REQ__${globalName}`, callback);
48
+ };
49
+ }
50
+ //# sourceMappingURL=exposeToRemote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exposeToRemote.js","sourceRoot":"","sources":["../../src/remoteValue/exposeToRemote.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAenD,SAAS,gBAAgB,CAAC,CAAqB;IAC7C,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,OAAO,SAAS,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAA;AACrD,CAAC;AAED,MAAM,UAAU,cAAc,CAAmB,GAAM,EAAE,OAAoB;IAC3E,MAAM,EACJ,UAAU,EACV,OAAO,EACP,QAAQ,EACR,SAAS,GAAG,gBAAgB,GAC7B,GAAG,OAAO,CAAA;IACX,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAqB,EAAE,EAAE;;QAC/C,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,CAAC,CAAC,CAAA;YAClB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9D,MAAM,IAAI,WAAW,CAAC,mBAAmB,CAAC,CAAA;YAC5C,CAAC;YACD,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,IAA4C,CAAA;YACtE,IAAI,MAAM,GAAG,GAAG,CAAA;YAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAwB,CAAkB,CAAA;YACnE,CAAC;YACD,IAAI,GAAY,CAAA;YAChB,IAAI,MAAM,YAAY,QAAQ,EAAE,CAAC;gBAC/B,GAAG,GAAG,MAAM,MAAM,CAAC,GAAG,IAAI,CAAC,CAAA;YAC7B,CAAC;iBAAM,CAAC;gBACN,GAAG,GAAG,MAAM,CAAA;YACd,CAAC;YACD,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,MAAA,CAAC,CAAC,YAAY,mCAAI,6BAA6B;gBACrD,QAAQ,EAAE,CAAC,CAAC,cAAc;gBAC1B,cAAc,EAAE,CAAC,CAAC,QAAQ;gBAC1B,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC;aAC5B,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,MAAA,CAAC,CAAC,YAAY,mCAAI,6BAA6B;gBACrD,QAAQ,EAAE,CAAC,CAAC,cAAc;gBAC1B,cAAc,EAAE,CAAC,CAAC,QAAQ;gBAC1B,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;aACnD,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,CAAA;IACD,OAAO,CAAC,EAAE,CAAC,uBAAuB,UAAU,EAAE,EAAE,QAAQ,CAAC,CAAA;IACzD,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,GAAG,CAAC,uBAAuB,UAAU,EAAE,EAAE,QAAQ,CAAC,CAAA;IAC5D,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { Adaptor, AdaptorPackageData } from '../adaptor';
2
+ import type { ToFunc } from './type';
3
+ type RemoteValueProps = Pick<AdaptorPackageData, 'deviceId' | 'targetDeviceId'> & {
4
+ globalName: string;
5
+ adaptor: Adaptor;
6
+ timeoutMs?: number;
7
+ log?: boolean;
8
+ };
9
+ /**
10
+ * @example
11
+ * ```
12
+ * const obj = remoteValue<{
13
+ * a: number;
14
+ * b: {
15
+ * c: string;
16
+ * };
17
+ * funcD: () => number
18
+ * }>()
19
+ *
20
+ * const val_1 = await obj.a() // number
21
+ * const val_2 = await obj.b() // { c: string }
22
+ * const val_3 = await obj.b.c() // string
23
+ * const val_4 = await obj.funcD() // number
24
+ * ```
25
+ */
26
+ export declare function remoteValue<T extends object>(props: RemoteValueProps): ToFunc<T>;
27
+ export {};
@@ -0,0 +1,68 @@
1
+ import { RemoteError, RemoteTimeoutError } from '../response';
2
+ function noop() {
3
+ // pass
4
+ }
5
+ function geneProxy(paths, props) {
6
+ return new Proxy(noop, {
7
+ apply(target, thisArg, argArray) {
8
+ const { adaptor, globalName, timeoutMs = 30000, log = false, ...restProps } = props;
9
+ const randomStr = Math.random().toString(36).slice(2);
10
+ const name = `__REMOTE_VALUE_REQ__${globalName}`;
11
+ const responseName = `__REMOTE_VALUE_RES__${[globalName, ...paths].join('.')}-${randomStr}`;
12
+ if (log) {
13
+ console.log(`[remoteValue] [${restProps.deviceId}] 正在访问远端 [${restProps.targetDeviceId}] 的变量: "${[globalName, ...paths].join('.')}"`);
14
+ }
15
+ return new Promise((resolve, reject) => {
16
+ let timer;
17
+ const callback = (e) => {
18
+ var _a;
19
+ clearTimeout(timer);
20
+ if (RemoteError.isRemoteError(e.data)) {
21
+ reject(RemoteError.fromError(e.data));
22
+ }
23
+ else {
24
+ resolve((_a = e.data) === null || _a === void 0 ? void 0 : _a.data);
25
+ }
26
+ };
27
+ adaptor.once(responseName, callback);
28
+ timer = setTimeout(() => {
29
+ adaptor.off(responseName, callback);
30
+ reject(new RemoteTimeoutError(`timeout: ${[globalName, ...paths].join('.')}`));
31
+ }, timeoutMs);
32
+ adaptor.emit({
33
+ name,
34
+ callbackName: responseName,
35
+ ...restProps,
36
+ data: {
37
+ paths,
38
+ args: argArray,
39
+ },
40
+ });
41
+ });
42
+ },
43
+ get(target, prop) {
44
+ return geneProxy([...paths, prop], props);
45
+ },
46
+ });
47
+ }
48
+ /**
49
+ * @example
50
+ * ```
51
+ * const obj = remoteValue<{
52
+ * a: number;
53
+ * b: {
54
+ * c: string;
55
+ * };
56
+ * funcD: () => number
57
+ * }>()
58
+ *
59
+ * const val_1 = await obj.a() // number
60
+ * const val_2 = await obj.b() // { c: string }
61
+ * const val_3 = await obj.b.c() // string
62
+ * const val_4 = await obj.funcD() // number
63
+ * ```
64
+ */
65
+ export function remoteValue(props) {
66
+ return geneProxy([], props);
67
+ }
68
+ //# sourceMappingURL=remoteValue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remoteValue.js","sourceRoot":"","sources":["../../src/remoteValue/remoteValue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAY,MAAM,aAAa,CAAA;AAIvE,SAAS,IAAI;IACX,OAAO;AACT,CAAC;AAYD,SAAS,SAAS,CAAmB,KAAe,EAAE,KAAuB;IAC3E,OAAO,IAAI,KAAK,CAAY,IAA4B,EAAE;QACxD,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ;YAC7B,MAAM,EACJ,OAAO,EACP,UAAU,EACV,SAAS,GAAG,KAAK,EACjB,GAAG,GAAG,KAAK,EACX,GAAG,SAAS,EACb,GAAG,KAAK,CAAA;YACT,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YACrD,MAAM,IAAI,GAAG,uBAAuB,UAAU,EAAE,CAAA;YAChD,MAAM,YAAY,GAAG,uBAAuB,CAAC,UAAU,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,EAAE,CAAA;YAE3F,IAAI,GAAG,EAAE,CAAC;gBACR,OAAO,CAAC,GAAG,CACT,kBAAkB,SAAS,CAAC,QAAQ,aAAa,SAAS,CAAC,cAAc,WAAW,CAAC,UAAU,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CACxH,CAAA;YACH,CAAC;YAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACrC,IAAI,KAAiC,CAAA;gBACrC,MAAM,QAAQ,GAAoB,CAAC,CAAC,EAAE,EAAE;;oBACtC,YAAY,CAAC,KAAK,CAAC,CAAA;oBACnB,IAAI,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;wBACtC,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;oBACvC,CAAC;yBAAM,CAAC;wBACN,OAAO,CAAC,MAAC,CAAC,CAAC,IAA4C,0CAAE,IAAI,CAAC,CAAA;oBAChE,CAAC;gBACH,CAAC,CAAA;gBACD,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAA;gBACpC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;oBACtB,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAA;oBACnC,MAAM,CACJ,IAAI,kBAAkB,CACpB,YAAY,CAAC,UAAU,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAC/C,CACF,CAAA;gBACH,CAAC,EAAE,SAAS,CAAC,CAAA;gBACb,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI;oBACJ,YAAY,EAAE,YAAY;oBAC1B,GAAG,SAAS;oBACZ,IAAI,EAAE;wBACJ,KAAK;wBACL,IAAI,EAAE,QAAQ;qBACf;iBACF,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,GAAG,CAAC,MAAM,EAAE,IAAI;YACd,OAAO,SAAS,CAAC,CAAC,GAAG,KAAK,EAAE,IAAc,CAAC,EAAE,KAAK,CAAC,CAAA;QACrD,CAAC;KACF,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,WAAW,CAAmB,KAAuB;IACnE,OAAO,SAAS,CAAI,EAAE,EAAE,KAAK,CAAC,CAAA;AAChC,CAAC"}
@@ -0,0 +1,62 @@
1
+ type HasFunc<T> = T extends (...args: unknown[]) => unknown ? true : T extends unknown[] ? {
2
+ [K in keyof T]: HasFunc<T[K]>;
3
+ }[number] extends false ? false : true : T extends object ? {
4
+ [K in keyof T]: HasFunc<T[K]>;
5
+ }[keyof T] extends false ? false : true : false;
6
+ export type ToFunc<T extends object> = {
7
+ [K in keyof T]: T[K] extends (...args: infer Args) => infer Ret ? (...args: Args) => Promise<Awaited<Ret>> : HasFunc<T[K]> extends false ? () => Promise<T[K]> : T[K] extends object ? ToFunc<T[K]> : never;
8
+ };
9
+ type Assert<T extends true> = T;
10
+ interface TestObj {
11
+ a1: number;
12
+ a2: number[];
13
+ a3: [number, string];
14
+ a4: {
15
+ b: number;
16
+ c: [string];
17
+ };
18
+ a5: () => Promise<number>;
19
+ a6: () => Promise<number[]>;
20
+ a7: () => Promise<[number, string]>;
21
+ b: {
22
+ b1: number;
23
+ b2: number[];
24
+ b3: [number, string];
25
+ b4: () => {
26
+ b: number;
27
+ c: [string];
28
+ };
29
+ b5: () => Promise<number>;
30
+ b6: () => Promise<number[]>;
31
+ b7: () => Promise<[number, string]>;
32
+ };
33
+ f: {
34
+ b: number;
35
+ c: {
36
+ d: () => number;
37
+ };
38
+ };
39
+ }
40
+ export type TestA1 = Assert<ToFunc<TestObj>['a1'] extends () => Promise<number> ? true : false>;
41
+ export type TestA2 = Assert<ToFunc<TestObj>['a2'] extends () => Promise<number[]> ? true : false>;
42
+ export type TestA3 = Assert<ToFunc<TestObj>['a3'] extends () => Promise<[number, string]> ? true : false>;
43
+ export type TestA4 = Assert<ToFunc<TestObj>['a4'] extends () => Promise<{
44
+ b: number;
45
+ c: [string];
46
+ }> ? true : false>;
47
+ export type TestA5 = Assert<ToFunc<TestObj>['a5'] extends () => Promise<number> ? true : false>;
48
+ export type TestA6 = Assert<ToFunc<TestObj>['a6'] extends () => Promise<number[]> ? true : false>;
49
+ export type TestA7 = Assert<ToFunc<TestObj>['a7'] extends () => Promise<[number, string]> ? true : false>;
50
+ export type TestB1 = Assert<ToFunc<TestObj>['b']['b1'] extends () => Promise<number> ? true : false>;
51
+ export type TestB2 = Assert<ToFunc<TestObj>['b']['b2'] extends () => Promise<number[]> ? true : false>;
52
+ export type TestB3 = Assert<ToFunc<TestObj>['b']['b3'] extends () => Promise<[number, string]> ? true : false>;
53
+ export type TestB4 = Assert<ToFunc<TestObj>['b']['b4'] extends () => Promise<{
54
+ b: number;
55
+ c: [string];
56
+ }> ? true : false>;
57
+ export type TestB5 = Assert<ToFunc<TestObj>['b']['b5'] extends () => Promise<number> ? true : false>;
58
+ export type TestB6 = Assert<ToFunc<TestObj>['b']['b6'] extends () => Promise<number[]> ? true : false>;
59
+ export type TestB7 = Assert<ToFunc<TestObj>['b']['b7'] extends () => Promise<[number, string]> ? true : false>;
60
+ export type TestF = Assert<ToFunc<TestObj>['f']['b'] extends () => Promise<number> ? true : false>;
61
+ export type TestF2 = Assert<ToFunc<TestObj>['f']['c']['d'] extends () => Promise<number> ? true : false>;
62
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.js","sourceRoot":"","sources":["../../src/remoteValue/type.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zimi/remote",
3
3
  "license": "MIT",
4
- "version": "0.2.0",
4
+ "version": "0.2.1-alpha.10",
5
5
  "author": "xiaomingTang",
6
6
  "description": "call remote functions as local",
7
7
  "private": false,
@@ -20,7 +20,7 @@ export interface AdaptorPackageData {
20
20
 
21
21
  type Func<D = unknown, R = unknown> = (data: D) => R
22
22
 
23
- type AdaptorCallback = Func<AdaptorPackageData, void>
23
+ export type AdaptorCallback = Func<AdaptorPackageData, void>
24
24
 
25
25
  export interface Adaptor {
26
26
  every: (callback: AdaptorCallback) => void
@@ -28,6 +28,7 @@ export interface Adaptor {
28
28
  * off 用于移除 once 注册的事件,当事件超时后,需要主动 off
29
29
  */
30
30
  off: (name: string, callback: AdaptorCallback) => void
31
+ on: (name: string, callback: AdaptorCallback) => void
31
32
  once: (name: string, callback: AdaptorCallback) => void
32
33
  emit: (data: AdaptorPackageData) => void
33
34
  }
@@ -0,0 +1,53 @@
1
+ import EventEmitter from 'eventemitter3'
2
+ import { isRemoteAdaptorData } from '../../remote'
3
+ import { type AdaptorPackageData } from '../../adaptor'
4
+
5
+ interface RemoteChannel {
6
+ onClientEvent(fn: (e: unknown) => void): void
7
+ }
8
+
9
+ /**
10
+ * 这是 [dao3 游戏](https://dao3.fun/) 代码客户端适配器。
11
+ * ```ts
12
+ * const remoteManager = new ClientSideRemoteChannelEventManager(remoteChannel)
13
+ *
14
+ * export const remoteAdaptor = {
15
+ * every: remoteManager.onEvery.bind(remoteManager),
16
+ * off: remoteManager.off.bind(remoteManager),
17
+ * on: remoteManager.on.bind(remoteManager),
18
+ * once: remoteManager.once.bind(remoteManager),
19
+ * emit: (e) => {
20
+ * remoteChannel.sendServerEvent(e)
21
+ * },
22
+ * } satisfies Adaptor
23
+ *
24
+ * export const remote = new Remote<RemoteFuncsFromClient, RemoteFuncsFromServer>(
25
+ * remoteAdaptor,
26
+ * {
27
+ * deviceId: `client-${Math.random().toString(36).slice(2)}`,
28
+ * }
29
+ * )
30
+ *
31
+ * void remote._.initConnection()
32
+ * ```
33
+ */
34
+ export class ClientSideRemoteChannelEventManager extends EventEmitter<{
35
+ [key: string]: [AdaptorPackageData]
36
+ }> {
37
+ constructor(remoteChannel: RemoteChannel) {
38
+ super()
39
+ remoteChannel.onClientEvent((e) => {
40
+ if (!isRemoteAdaptorData(e)) {
41
+ return
42
+ }
43
+ if (!e.name.startsWith('__REMOTE_VALUE_REQ__')) {
44
+ this.emit('__remote_every__', e)
45
+ }
46
+ this.emit(e.name, e)
47
+ })
48
+ }
49
+
50
+ onEvery(fn: (args: AdaptorPackageData) => void): void {
51
+ this.on('__remote_every__', fn)
52
+ }
53
+ }
@@ -0,0 +1,136 @@
1
+ import EventEmitter from 'eventemitter3'
2
+ import { isRemoteAdaptorData } from '../../remote'
3
+ import { isRemoteValueEvent } from '../../remoteValue/exposeToRemote'
4
+ import { type AdaptorPackageData } from '../../adaptor'
5
+
6
+ interface GamePlayerEntityLike {
7
+ player: {
8
+ userKey: string
9
+ }
10
+ }
11
+
12
+ interface RemoteChannel<T extends GamePlayerEntityLike> {
13
+ onServerEvent(fn: (e: { args: unknown; entity: T }) => void): void
14
+ }
15
+
16
+ /**
17
+ * 这是 [dao3 游戏](https://dao3.fun/) 代码服务端适配器。
18
+ * ```ts
19
+ * const remoteManager = new ServerSideRemoteChannelEventManager(remoteChannel)
20
+ *
21
+ * world.onPlayerLeave(
22
+ * remoteManager.onPlayerLeave.bind(remoteManager)
23
+ * )
24
+ *
25
+ * export const remoteAdaptor = {
26
+ * every: remoteManager.onEvery.bind(remoteManager),
27
+ * off: remoteManager.off.bind(remoteManager),
28
+ * on: remoteManager.on.bind(remoteManager),
29
+ * once: remoteManager.once.bind(remoteManager),
30
+ * emit: (e) => {
31
+ * const entity = remoteManager.getEntity(e.targetDeviceId)
32
+ * if (!entity) {
33
+ * console.error('entity not found')
34
+ * return
35
+ * }
36
+ * remoteChannel.sendClientEvent(entity, e as unknown as JSONValue)
37
+ * },
38
+ * } satisfies Adaptor
39
+ *
40
+ * export const remote = new Remote<RemoteFuncsFromServer, RemoteFuncsFromClient>(
41
+ * remoteAdaptor,
42
+ * {
43
+ * deviceId: 'server',
44
+ * }
45
+ * )
46
+ *
47
+ * remote.register('initConnection', async () => {
48
+ * // 什么也不干,单纯的让客户端在服务端“备案”
49
+ * })
50
+ *
51
+ * export const getEntity = remoteManager.getEntity.bind(remoteManager)
52
+ * export const getIdByEntity = remoteManager.getIdByEntity.bind(remoteManager)
53
+ * export const waitForRegister = remoteManager.waitForRegister.bind(remoteManager)
54
+ * export const remoteTo = remoteManager.remoteTo.bind(remoteManager)
55
+ * ```
56
+ */
57
+ export class ServerSideRemoteChannelEventManager<
58
+ T extends GamePlayerEntityLike,
59
+ > extends EventEmitter<{
60
+ [key: string]: [AdaptorPackageData]
61
+ }> {
62
+ entityMap: [string, T][] = []
63
+
64
+ constructor(remoteChannel: RemoteChannel<T>) {
65
+ super()
66
+ remoteChannel.onServerEvent((e) => {
67
+ const { args } = e
68
+ if (!isRemoteAdaptorData(args)) {
69
+ return
70
+ }
71
+ const prevItem = this.entityMap.find(
72
+ (item) => item[1].player.userKey === e.entity.player.userKey
73
+ )
74
+ if (prevItem) {
75
+ prevItem[0] = args.deviceId
76
+ prevItem[1] = e.entity
77
+ } else {
78
+ this.entityMap.push([args.deviceId, e.entity])
79
+ }
80
+ if (!isRemoteValueEvent(args.name)) {
81
+ this.emit('__remote_every__', args)
82
+ }
83
+ this.emit(args.name, args)
84
+ })
85
+ }
86
+
87
+ onPlayerLeave(e: { entity: T }) {
88
+ this.entityMap = this.entityMap.filter(
89
+ (item) => item[1].player.userKey !== e.entity.player.userKey
90
+ )
91
+ }
92
+
93
+ onEvery(fn: (args: AdaptorPackageData) => void): void {
94
+ this.on('__remote_every__', fn)
95
+ }
96
+
97
+ waitForRegister(entity: T) {
98
+ return new Promise<void>((resolve) => {
99
+ if (
100
+ this.entityMap.some(
101
+ ([_, en]) => en.player.userKey === entity.player.userKey
102
+ )
103
+ ) {
104
+ resolve()
105
+ return
106
+ }
107
+ this.once('__remote_every__', () => {
108
+ resolve()
109
+ })
110
+ })
111
+ }
112
+
113
+ getEntity(deviceId: string): T | undefined {
114
+ return this.entityMap.find((item) => item[0] === deviceId)?.[1]
115
+ }
116
+
117
+ getIdByEntity(entity: T): string | undefined {
118
+ return this.entityMap.find(
119
+ (item) => item[1].player.userKey === entity.player.userKey
120
+ )?.[0]
121
+ }
122
+
123
+ remoteTo(config: { target: T }): {
124
+ targetDeviceId: string
125
+ }
126
+ remoteTo(config: { target: T; timeoutMs: number }): {
127
+ targetDeviceId: string
128
+ timeoutMs: number
129
+ }
130
+ remoteTo({ target, ...rest }: { target: T; timeoutMs?: number }) {
131
+ return {
132
+ targetDeviceId: this.getIdByEntity(target),
133
+ ...rest,
134
+ }
135
+ }
136
+ }
@@ -21,6 +21,7 @@ export function createHttpAdaptor({
21
21
  }) {
22
22
  const adaptor: Adaptor = {
23
23
  every: remoteEventManager.onEvery.bind(remoteEventManager),
24
+ on: remoteEventManager.on.bind(remoteEventManager),
24
25
  once: remoteEventManager.once.bind(remoteEventManager),
25
26
  off: remoteEventManager.off.bind(remoteEventManager),
26
27
  emit: onEmit,
@@ -37,6 +37,7 @@ export function createIframeAdaptor({
37
37
 
38
38
  const adaptor: Adaptor = {
39
39
  every: remoteEventManager.onEvery.bind(remoteEventManager),
40
+ on: remoteEventManager.on.bind(remoteEventManager),
40
41
  once: remoteEventManager.once.bind(remoteEventManager),
41
42
  off: remoteEventManager.off.bind(remoteEventManager),
42
43
  emit: onEmit,
package/src/index.ts CHANGED
@@ -1,10 +1,12 @@
1
- export { Remote, isRemoteAdaptorData } from './remote'
2
- export type { AdaptorPackageData, Adaptor } from './adaptor'
3
- export {
4
- RemoteError,
5
- RemoteNotFoundError,
6
- RemoteTimeoutError,
7
- response,
8
- } from './response'
9
- export { createIframeAdaptor } from './adaptors/iframe'
10
- export { createHttpAdaptor, remoteEventManager } from './adaptors/http'
1
+ export * from './remote'
2
+ export * from './adaptor'
3
+ export * from './response'
4
+
5
+ export * from './remoteValue/remoteValue'
6
+ export * from './remoteValue/exposeToRemote'
7
+ export type { ToFunc } from './remoteValue/type'
8
+
9
+ export * from './adaptors/iframe'
10
+ export * from './adaptors/http'
11
+ export * from './adaptors/dao3/client'
12
+ export * from './adaptors/dao3/server'
@@ -0,0 +1,68 @@
1
+ import { Adaptor, AdaptorPackageData } from '../adaptor'
2
+ import { RemoteError, response } from '../response'
3
+
4
+ interface ExposeProps {
5
+ globalName: string
6
+ adaptor: Adaptor
7
+ /**
8
+ * ['*'] or ['device_id_1', 'device_id_2']
9
+ */
10
+ exposeTo: string[]
11
+ /**
12
+ * 你可以在该回调中抛错,以阻止远程调用
13
+ */
14
+ onRequest?: (e: AdaptorPackageData) => void | Promise<void>
15
+ }
16
+
17
+ function defaultOnRequest(e: AdaptorPackageData) {
18
+ return e
19
+ }
20
+
21
+ export function isRemoteValueEvent(eventName: string) {
22
+ return eventName.startsWith('__REMOTE_VALUE_REQ__')
23
+ }
24
+
25
+ export function exposeToRemote<T extends object>(obj: T, options: ExposeProps) {
26
+ const {
27
+ globalName,
28
+ adaptor,
29
+ exposeTo,
30
+ onRequest = defaultOnRequest,
31
+ } = options
32
+ const callback = async (e: AdaptorPackageData) => {
33
+ try {
34
+ await onRequest(e)
35
+ if (!exposeTo.includes(e.deviceId) && !exposeTo.includes('*')) {
36
+ throw new RemoteError('permission denied')
37
+ }
38
+ const { paths, args } = e.data as { paths: string[]; args: unknown[] }
39
+ let target = obj
40
+ for (let i = 0; i < paths.length; i += 1) {
41
+ target = target[paths[i] as keyof typeof target] as typeof target
42
+ }
43
+ let res: unknown
44
+ if (target instanceof Function) {
45
+ res = await target(...args)
46
+ } else {
47
+ res = target
48
+ }
49
+ adaptor.emit({
50
+ name: e.callbackName ?? 'IMPOSSIBLE_NO_CALLBACK_NAME',
51
+ deviceId: e.targetDeviceId,
52
+ targetDeviceId: e.deviceId,
53
+ data: response.success(res),
54
+ })
55
+ } catch (error) {
56
+ adaptor.emit({
57
+ name: e.callbackName ?? 'IMPOSSIBLE_NO_CALLBACK_NAME',
58
+ deviceId: e.targetDeviceId,
59
+ targetDeviceId: e.deviceId,
60
+ data: response.error(RemoteError.fromError(error)),
61
+ })
62
+ }
63
+ }
64
+ adaptor.on(`__REMOTE_VALUE_REQ__${globalName}`, callback)
65
+ return () => {
66
+ adaptor.off(`__REMOTE_VALUE_REQ__${globalName}`, callback)
67
+ }
68
+ }
@@ -0,0 +1,94 @@
1
+ import { RemoteError, RemoteTimeoutError, response } from '../response'
2
+ import { Adaptor, AdaptorCallback, AdaptorPackageData } from '../adaptor'
3
+ import type { ToFunc } from './type'
4
+
5
+ function noop() {
6
+ // pass
7
+ }
8
+
9
+ type RemoteValueProps = Pick<
10
+ AdaptorPackageData,
11
+ 'deviceId' | 'targetDeviceId'
12
+ > & {
13
+ globalName: string
14
+ adaptor: Adaptor
15
+ timeoutMs?: number
16
+ log?: boolean
17
+ }
18
+
19
+ function geneProxy<T extends object>(paths: string[], props: RemoteValueProps) {
20
+ return new Proxy<ToFunc<T>>(noop as unknown as ToFunc<T>, {
21
+ apply(target, thisArg, argArray) {
22
+ const {
23
+ adaptor,
24
+ globalName,
25
+ timeoutMs = 30000,
26
+ log = false,
27
+ ...restProps
28
+ } = props
29
+ const randomStr = Math.random().toString(36).slice(2)
30
+ const name = `__REMOTE_VALUE_REQ__${globalName}`
31
+ const responseName = `__REMOTE_VALUE_RES__${[globalName, ...paths].join('.')}-${randomStr}`
32
+
33
+ if (log) {
34
+ console.log(
35
+ `[remoteValue] [${restProps.deviceId}] 正在访问远端 [${restProps.targetDeviceId}] 的变量: "${[globalName, ...paths].join('.')}"`
36
+ )
37
+ }
38
+
39
+ return new Promise((resolve, reject) => {
40
+ let timer: NodeJS.Timeout | undefined
41
+ const callback: AdaptorCallback = (e) => {
42
+ clearTimeout(timer)
43
+ if (RemoteError.isRemoteError(e.data)) {
44
+ reject(RemoteError.fromError(e.data))
45
+ } else {
46
+ resolve((e.data as ReturnType<typeof response.success>)?.data)
47
+ }
48
+ }
49
+ adaptor.once(responseName, callback)
50
+ timer = setTimeout(() => {
51
+ adaptor.off(responseName, callback)
52
+ reject(
53
+ new RemoteTimeoutError(
54
+ `timeout: ${[globalName, ...paths].join('.')}`
55
+ )
56
+ )
57
+ }, timeoutMs)
58
+ adaptor.emit({
59
+ name,
60
+ callbackName: responseName,
61
+ ...restProps,
62
+ data: {
63
+ paths,
64
+ args: argArray,
65
+ },
66
+ })
67
+ })
68
+ },
69
+ get(target, prop) {
70
+ return geneProxy([...paths, prop as string], props)
71
+ },
72
+ })
73
+ }
74
+
75
+ /**
76
+ * @example
77
+ * ```
78
+ * const obj = remoteValue<{
79
+ * a: number;
80
+ * b: {
81
+ * c: string;
82
+ * };
83
+ * funcD: () => number
84
+ * }>()
85
+ *
86
+ * const val_1 = await obj.a() // number
87
+ * const val_2 = await obj.b() // { c: string }
88
+ * const val_3 = await obj.b.c() // string
89
+ * const val_4 = await obj.funcD() // number
90
+ * ```
91
+ */
92
+ export function remoteValue<T extends object>(props: RemoteValueProps) {
93
+ return geneProxy<T>([], props)
94
+ }
@@ -0,0 +1,119 @@
1
+ type HasFunc<T> = T extends (...args: unknown[]) => unknown
2
+ ? true
3
+ : T extends unknown[]
4
+ ? { [K in keyof T]: HasFunc<T[K]> }[number] extends false
5
+ ? false
6
+ : true
7
+ : T extends object
8
+ ? { [K in keyof T]: HasFunc<T[K]> }[keyof T] extends false
9
+ ? false
10
+ : true
11
+ : false
12
+
13
+ export type ToFunc<T extends object> = {
14
+ [K in keyof T]: T[K] extends (...args: infer Args) => infer Ret
15
+ ? (...args: Args) => Promise<Awaited<Ret>>
16
+ : HasFunc<T[K]> extends false
17
+ ? () => Promise<T[K]>
18
+ : T[K] extends object
19
+ ? ToFunc<T[K]>
20
+ : never
21
+ }
22
+
23
+ // 下面的都是测试代码,用于测试 ToFunc
24
+
25
+ type Assert<T extends true> = T
26
+
27
+ interface TestObj {
28
+ a1: number
29
+ a2: number[]
30
+ a3: [number, string]
31
+ a4: {
32
+ b: number
33
+ c: [string]
34
+ }
35
+ a5: () => Promise<number>
36
+ a6: () => Promise<number[]>
37
+ a7: () => Promise<[number, string]>
38
+ b: {
39
+ b1: number
40
+ b2: number[]
41
+ b3: [number, string]
42
+ b4: () => {
43
+ b: number
44
+ c: [string]
45
+ }
46
+ b5: () => Promise<number>
47
+ b6: () => Promise<number[]>
48
+ b7: () => Promise<[number, string]>
49
+ }
50
+ f: {
51
+ b: number
52
+ c: {
53
+ d: () => number
54
+ }
55
+ }
56
+ }
57
+
58
+ export type TestA1 = Assert<
59
+ ToFunc<TestObj>['a1'] extends () => Promise<number> ? true : false
60
+ >
61
+ export type TestA2 = Assert<
62
+ ToFunc<TestObj>['a2'] extends () => Promise<number[]> ? true : false
63
+ >
64
+ export type TestA3 = Assert<
65
+ ToFunc<TestObj>['a3'] extends () => Promise<[number, string]> ? true : false
66
+ >
67
+ export type TestA4 = Assert<
68
+ ToFunc<TestObj>['a4'] extends () => Promise<{
69
+ b: number
70
+ c: [string]
71
+ }>
72
+ ? true
73
+ : false
74
+ >
75
+ export type TestA5 = Assert<
76
+ ToFunc<TestObj>['a5'] extends () => Promise<number> ? true : false
77
+ >
78
+ export type TestA6 = Assert<
79
+ ToFunc<TestObj>['a6'] extends () => Promise<number[]> ? true : false
80
+ >
81
+ export type TestA7 = Assert<
82
+ ToFunc<TestObj>['a7'] extends () => Promise<[number, string]> ? true : false
83
+ >
84
+ export type TestB1 = Assert<
85
+ ToFunc<TestObj>['b']['b1'] extends () => Promise<number> ? true : false
86
+ >
87
+ export type TestB2 = Assert<
88
+ ToFunc<TestObj>['b']['b2'] extends () => Promise<number[]> ? true : false
89
+ >
90
+ export type TestB3 = Assert<
91
+ ToFunc<TestObj>['b']['b3'] extends () => Promise<[number, string]>
92
+ ? true
93
+ : false
94
+ >
95
+ export type TestB4 = Assert<
96
+ ToFunc<TestObj>['b']['b4'] extends () => Promise<{
97
+ b: number
98
+ c: [string]
99
+ }>
100
+ ? true
101
+ : false
102
+ >
103
+ export type TestB5 = Assert<
104
+ ToFunc<TestObj>['b']['b5'] extends () => Promise<number> ? true : false
105
+ >
106
+ export type TestB6 = Assert<
107
+ ToFunc<TestObj>['b']['b6'] extends () => Promise<number[]> ? true : false
108
+ >
109
+ export type TestB7 = Assert<
110
+ ToFunc<TestObj>['b']['b7'] extends () => Promise<[number, string]>
111
+ ? true
112
+ : false
113
+ >
114
+ export type TestF = Assert<
115
+ ToFunc<TestObj>['f']['b'] extends () => Promise<number> ? true : false
116
+ >
117
+ export type TestF2 = Assert<
118
+ ToFunc<TestObj>['f']['c']['d'] extends () => Promise<number> ? true : false
119
+ >