@zimi/remote 0.2.1 → 0.2.4

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.
Files changed (115) hide show
  1. package/LICENSE.md +21 -21
  2. package/README.md +289 -285
  3. package/dist/adaptor.d.mts +33 -0
  4. package/dist/adaptor.d.ts +5 -4
  5. package/dist/adaptor.js +1 -1
  6. package/dist/adaptor.js.map +1 -1
  7. package/dist/adaptor.mjs +1 -0
  8. package/dist/adaptor.mjs.map +1 -0
  9. package/dist/adaptors/dao3/client.d.mts +39 -0
  10. package/dist/adaptors/dao3/client.d.ts +5 -3
  11. package/dist/adaptors/dao3/client.js +1 -44
  12. package/dist/adaptors/dao3/client.js.map +1 -1
  13. package/dist/adaptors/dao3/client.mjs +2 -0
  14. package/dist/adaptors/dao3/client.mjs.map +1 -0
  15. package/dist/adaptors/dao3/server.d.mts +82 -0
  16. package/dist/adaptors/dao3/server.d.ts +5 -3
  17. package/dist/adaptors/dao3/server.js +1 -104
  18. package/dist/adaptors/dao3/server.js.map +1 -1
  19. package/dist/adaptors/dao3/server.mjs +2 -0
  20. package/dist/adaptors/dao3/server.mjs.map +1 -0
  21. package/dist/adaptors/electron/constants.d.mts +5 -0
  22. package/dist/adaptors/electron/constants.d.ts +5 -0
  23. package/dist/adaptors/electron/constants.js +2 -0
  24. package/dist/adaptors/electron/constants.js.map +1 -0
  25. package/dist/adaptors/electron/constants.mjs +2 -0
  26. package/dist/adaptors/electron/constants.mjs.map +1 -0
  27. package/dist/adaptors/electron/main.d.mts +10 -0
  28. package/dist/adaptors/electron/main.d.ts +10 -0
  29. package/dist/adaptors/electron/main.js +2 -0
  30. package/dist/adaptors/electron/main.js.map +1 -0
  31. package/dist/adaptors/electron/main.mjs +2 -0
  32. package/dist/adaptors/electron/main.mjs.map +1 -0
  33. package/dist/adaptors/electron/messenger.d.mts +8 -0
  34. package/dist/adaptors/electron/messenger.d.ts +8 -0
  35. package/dist/adaptors/electron/messenger.js +2 -0
  36. package/dist/adaptors/electron/messenger.js.map +1 -0
  37. package/dist/adaptors/electron/messenger.mjs +2 -0
  38. package/dist/adaptors/electron/messenger.mjs.map +1 -0
  39. package/dist/adaptors/electron/preload.d.mts +6 -0
  40. package/dist/adaptors/electron/preload.d.ts +6 -0
  41. package/dist/adaptors/electron/preload.js +2 -0
  42. package/dist/adaptors/electron/preload.js.map +1 -0
  43. package/dist/adaptors/electron/preload.mjs +2 -0
  44. package/dist/adaptors/electron/preload.mjs.map +1 -0
  45. package/dist/adaptors/electron/renderer.d.mts +9 -0
  46. package/dist/adaptors/electron/renderer.d.ts +9 -0
  47. package/dist/adaptors/electron/renderer.js +2 -0
  48. package/dist/adaptors/electron/renderer.js.map +1 -0
  49. package/dist/adaptors/electron/renderer.mjs +2 -0
  50. package/dist/adaptors/electron/renderer.mjs.map +1 -0
  51. package/dist/adaptors/http.d.mts +15 -0
  52. package/dist/adaptors/http.d.ts +6 -4
  53. package/dist/adaptors/http.js +1 -26
  54. package/dist/adaptors/http.js.map +1 -1
  55. package/dist/adaptors/http.mjs +2 -0
  56. package/dist/adaptors/http.mjs.map +1 -0
  57. package/dist/adaptors/iframe.d.mts +7 -0
  58. package/dist/adaptors/iframe.d.ts +5 -2
  59. package/dist/adaptors/iframe.js +1 -38
  60. package/dist/adaptors/iframe.js.map +1 -1
  61. package/dist/adaptors/iframe.mjs +2 -0
  62. package/dist/adaptors/iframe.mjs.map +1 -0
  63. package/dist/index.d.mts +16 -0
  64. package/dist/index.d.ts +16 -10
  65. package/dist/index.js +1 -9
  66. package/dist/index.js.map +1 -1
  67. package/dist/index.mjs +2 -0
  68. package/dist/index.mjs.map +1 -0
  69. package/dist/remote.d.mts +81 -0
  70. package/dist/remote.d.ts +6 -4
  71. package/dist/remote.js +1 -196
  72. package/dist/remote.js.map +1 -1
  73. package/dist/remote.mjs +2 -0
  74. package/dist/remote.mjs.map +1 -0
  75. package/dist/remoteValue/exposeToRemote.d.mts +43 -0
  76. package/dist/remoteValue/exposeToRemote.d.ts +7 -5
  77. package/dist/remoteValue/exposeToRemote.js +1 -52
  78. package/dist/remoteValue/exposeToRemote.js.map +1 -1
  79. package/dist/remoteValue/exposeToRemote.mjs +2 -0
  80. package/dist/remoteValue/exposeToRemote.mjs.map +1 -0
  81. package/dist/remoteValue/remoteValue.d.mts +29 -0
  82. package/dist/remoteValue/remoteValue.d.ts +6 -4
  83. package/dist/remoteValue/remoteValue.js +1 -67
  84. package/dist/remoteValue/remoteValue.js.map +1 -1
  85. package/dist/remoteValue/remoteValue.mjs +2 -0
  86. package/dist/remoteValue/remoteValue.mjs.map +1 -0
  87. package/dist/remoteValue/type.d.mts +67 -0
  88. package/dist/remoteValue/type.d.ts +20 -19
  89. package/dist/remoteValue/type.js +1 -1
  90. package/dist/remoteValue/type.js.map +1 -1
  91. package/dist/remoteValue/type.mjs +1 -0
  92. package/dist/remoteValue/type.mjs.map +1 -0
  93. package/dist/response.d.mts +105 -0
  94. package/dist/response.d.ts +6 -5
  95. package/dist/response.js +1 -154
  96. package/dist/response.js.map +1 -1
  97. package/dist/response.mjs +2 -0
  98. package/dist/response.mjs.map +1 -0
  99. package/package.json +31 -24
  100. package/src/adaptor.ts +34 -34
  101. package/src/adaptors/dao3/client.ts +53 -53
  102. package/src/adaptors/dao3/server.ts +136 -136
  103. package/src/adaptors/electron/constants.ts +3 -0
  104. package/src/adaptors/electron/main.ts +59 -0
  105. package/src/adaptors/electron/messenger.ts +13 -0
  106. package/src/adaptors/electron/preload.ts +21 -0
  107. package/src/adaptors/electron/renderer.ts +52 -0
  108. package/src/adaptors/http.ts +31 -31
  109. package/src/adaptors/iframe.ts +47 -47
  110. package/src/index.ts +17 -12
  111. package/src/remote.ts +263 -260
  112. package/src/remoteValue/exposeToRemote.ts +102 -102
  113. package/src/remoteValue/remoteValue.ts +94 -94
  114. package/src/remoteValue/type.ts +124 -124
  115. package/src/response.ts +170 -170
package/LICENSE.md CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2021-2022 @zimi/utils authors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2021-2022 @zimi/utils authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,285 +1,289 @@
1
- > 代码见 [@zimi/remote](https://github.com/xiaomingTang/xiaoming/tree/master/%40zimi/remote)
2
-
3
- - 本地可以是浏览器、服务器,甚至一些受限的 `js` 子集
4
- - 远端可以是任何终端,如 `iframe` / `Java` 服务器 等
5
- - 对远端响应的数据格式也不严格限制(可以集中解析)
6
- - 已在公司游戏前后端通信中应用,极大地降低了通信成本(简化调用)
7
- - ts 类型严格
8
-
9
- ## install
10
- ```
11
- pnpm i @zimi/remote
12
- ```
13
-
14
- ## examples
15
-
16
- ### 调用示意
17
-
18
- ```ts
19
-
20
- // 远端
21
- remote.register('something', async (params: Whatever) => {
22
- return WhatYouWant
23
- })
24
-
25
- // 本地
26
- // res === WhatYouWant
27
- const res = await remote._.something(xxx)
28
-
29
- ```
30
-
31
- ### iframe 与父级相互调用
32
-
33
- ```ts
34
- // 1. 声明各自能提供的函数类型
35
- // type.d.ts
36
-
37
- // 父级能提供的函数
38
- export type FuncsFromParent = {
39
- plus: (data: [number, number]) => Promise<number>
40
- }
41
-
42
- // 子级能提供的函数
43
- export type FuncsFromChild = {
44
- multiply: (data: [number, number]) => Promise<number>
45
- }
46
- ```
47
-
48
- ```ts
49
- // 2. 父级 remote 初始化
50
- // parent.ts
51
-
52
- import { Remote, createIframeAdaptor } from '@zimi/remote'
53
-
54
- function getOpWindow() {
55
- return document.querySelector<HTMLIFrameElement>('#child-iframe')?.contentWindow
56
- }
57
-
58
- // 我们提供了生成 iframe adaptor 的工具函数
59
- // 你也可以参考实现自己的 adaptor, 没多少代码
60
- const adaptor = createIframeAdaptor({
61
- onEmit: (data) => {
62
- // 此处仅为示意,业务场景下应当限制对方的域名
63
- getOpWindow()?.postMessage(data, '*')
64
- },
65
- })
66
-
67
- const remote = new Remote<FuncsFromParent, FuncsFromChild>(adaptor, {
68
- deviceId: 'parent',
69
- })
70
-
71
- // 父级注册自己能提供的函数
72
- remote.register('plus', async ([a, b]) => a + b)
73
- ```
74
-
75
- ```ts
76
- // 3. 子级 remote 初始化
77
- // child-iframe.ts
78
-
79
- import { Remote, createIframeAdaptor } from '@zimi/remote'
80
-
81
- function getOpWindow() {
82
- return window.top
83
- }
84
-
85
- // 我们提供了生成 iframe adaptor 的工具函数
86
- // 你也可以参考实现自己的 adaptor, 没多少代码
87
- const adaptor = createIframeAdaptor({
88
- onEmit: (data) => {
89
- // 此处仅为示意,业务场景下应当限制对方的域名
90
- getOpWindow()?.postMessage(data, '*')
91
- },
92
- })
93
-
94
- const remote = new Remote<FuncsFromChild, FuncsFromParent>(adaptor, {
95
- // 当涉及到多子级时,可以通过该 deviceId 来区分彼此,
96
- // 达到与不同子级通信的效果
97
- deviceId: 'child',
98
- })
99
-
100
- // 子级注册自己能提供的函数
101
- remote.register('multiply', async ([a, b]) => a * b)
102
- ```
103
-
104
- ```ts
105
- // 好了,现在你可以父子间随意通信了
106
-
107
- // 对方所有函数都被代理到 remote._.xxx 上了
108
-
109
- // parent.ts
110
- // 父级中可以直接调用子级的函数
111
- // 有严格的类型与提示
112
- await remote._.multiply([3, 2])
113
-
114
- await remote._.multiply([3, 2], {
115
- // 每个函数可以单独指定超时时间,超时后会抛出 RemoteTimeoutError
116
- timeoutMs: 1000,
117
- // 每个函数可以指定调用特定目标所有的函数(需要在 adaptor onEmit 中根据 targetDeviceId 往不同设备发送消息)
118
- targetDeviceId: 'child-2'
119
- })
120
-
121
- // 调用对方未注册的函数,会抛出 RemoteNotFoundError
122
- await remote._.notRegisteredFunc()
123
-
124
- // 当对方函数发生运行时错误时,会抛出 RemoteError
125
- // 以上所有 error 都继承自 Error
126
- ```
127
-
128
- ### 浏览器与服务器通信
129
-
130
- ```ts
131
- // 对方怎么写我们就不管了,假设对方返回的数据格式为:
132
- interface JavaResponse {
133
- // 假设 code >= 300 为错误;code < 300 为成功
134
- code: number
135
- // 响应的数据
136
- data: unknown
137
- // 可能存在的错误信息
138
- errorMsg?: string
139
- }
140
- ```
141
-
142
- 此时 adaptor 的 onEmit 函数稍微有些复杂,它需要解析服务端响应的数据,并封装为我们需要的格式:
143
-
144
- ```ts
145
- const adaptor = createHttpAdaptor({
146
- onEmit: async (data) => {
147
- // 这里只是简单示意,使用者可以根据自己的情况构造 request
148
- const res = await fetch(`https://xxx.com/api/${data.name}`, {
149
- method: 'POST',
150
- body: JSON.stringify(data.data),
151
- headers: {
152
- 'Content-Type': 'application/json',
153
- },
154
- })
155
- // 下面相当于我们代 server 端封装了一下数据
156
- // callbackName 是一定会有的,此处只是为了类型安全
157
- const callbackName = data.callbackName ?? 'IMPOSSIBLE_NO_CALLBACK_NAME'
158
- const adaptorData: AdaptorPackageData = {
159
- // 由于我们代 server 抛出事件
160
- // 所以这里的 deviceId 和 targetDeviceId 是相反的
161
- deviceId: data.targetDeviceId,
162
- targetDeviceId: data.deviceId,
163
- name: callbackName,
164
- // 我们在下面根据不同情况来填充 data
165
- data: null,
166
- }
167
- if (!res.ok) {
168
- adaptorData.data = response.error(new RemoteError('network error'))
169
- } else {
170
- const json = (await res.json()) as {
171
- code: number
172
- data: unknown
173
- errorMsg?: string
174
- }
175
- if (json.code < 300) {
176
- adaptorData.data = response.success(json.data)
177
- } else {
178
- const error = new RemoteError(`server error: ${json.errorMsg}`)
179
- // RemoteError 也接受 code, 你可以把服务端响应的错误码挂到其上,便于业务上区分处理
180
- error.code = json.code
181
- adaptorData.data = response.error(error)
182
- }
183
- }
184
- // 一定要抛出 every 事件,remote 包基于此处理远端的响应
185
- remoteEventManager.emit(remoteEventManager.EVERY_EVENT_NAME, adaptorData)
186
- remoteEventManager.emit(callbackName, adaptorData)
187
- },
188
- })
189
-
190
- // 由于服务端不会调用我们,所以我们无需提供函数,自然也无需调用 remote.register 注册函数
191
- const remote = new Remote<{}, FuncsFromHttp>(adaptor, {
192
- deviceId: 'client',
193
- })
194
-
195
- // 使用方法同前
196
- await remote._.xxx(anyData)
197
- ```
198
-
199
- ### 与其他端通信(如 websocket)略
200
-
201
- 你可以看看 `iframe adaptor` / `http adaptor` 源码,包含空行也就 30 行,依葫芦画瓢很轻易就能写一个。
202
-
203
- ## rpc 相比的优势
204
-
205
- - 不局限于与服务端的通信,无论对方是任何端,只要能与 js 通信,就能使用该包;
206
- - 相互通信,不存在“主从”的概念,通信双方是平等的;
207
- - 类型严格;
208
- - 包较底层,对项目整体的侵入较小,几乎不限制对方的响应的数据格式(因为可以自由解析对方的响应,即自由 emit);
209
-
210
- ## 协议
211
-
212
- > 由于通信双方是平等的,所以 B 调用 A 的流程也是一样的
213
-
214
- ![protocol.png](https://cdn.16px.cc/public/2024-12-08/fm5up4GM9UCz.png?r=1682x836)
215
-
216
- ---
217
- ---
218
-
219
- ## remote value
220
-
221
- > 像使用 local value 一样使用 remote value
222
-
223
- **首先强调一遍,这个包是一个通信的包,不负责权限控制,需要使用者在业务层面进行恰当的处理,控制好什么该暴露,什么不该暴露。**
224
-
225
- 如果我们(暂且称为 端)需要访问其他端(可能是服务器,也可能是其他 iframe or whatever,暂且称为 乙 端)的一些数据/对象/方法,我们通常是这么做的:
226
-
227
- // 乙 端
228
- ```ts
229
- const obj = {
230
- value: number,
231
- setValue: (newValue: number) => {
232
- obj.value = newValue
233
- }
234
- }
235
-
236
- register({
237
- method: 'get',
238
- name: 'getValue',
239
- handler: () => {
240
- return obj.value
241
- }
242
- })
243
-
244
- register({
245
- method: 'post',
246
- name: 'setValue',
247
- handler: (newValue: string) => {
248
- obj.setValue(newValue)
249
- }
250
- })
251
- ```
252
-
253
- // 甲 端
254
- ```ts
255
- const value = await fetch.get('getValue')
256
- await fetch.post('setValue', newValue)
257
- ```
258
-
259
- 有没有感觉在重复劳动?我 端已经有了一个现成的`obj`,可是我还是需要在`register`处“封装”一下。
260
-
261
- ---
262
-
263
- 那么,**下面这个**怎么样:
264
-
265
- // 乙 端
266
- ```ts
267
- exposeToRemote(obj, config)
268
- ```
269
-
270
- // 甲 端
271
- ```ts
272
- import { type obj } from '/path/to/obj'
273
-
274
- const remoteObj = remoteValue<typeof obj>(config)
275
-
276
- // 很遗憾没能实现 await remoteObj.value 直接取值,
277
- // 无论是值还是函数,必须在后面加一个括号调用,才能取到结果。
278
- const value = await remoteObj.value()
279
- const subValue = await remoteObj.deep.path.to.value()
280
-
281
- await remoteObj.setValue(newValue)
282
- await remoteObj.deep.path.to.func(...params)
283
- ```
284
-
285
- **再次强调一遍,这个包是一个通信的包,不负责权限控制,需要使用者在业务层面进行恰当的处理,控制好什么该暴露,什么不该暴露。**
1
+ > 代码见 [@zimi/remote](https://github.com/xiaomingTang/xiaoming/tree/master/%40zimi/remote)
2
+
3
+ - 本地可以是浏览器、服务器,甚至一些受限的 `js` 子集
4
+ - 远端可以是任何终端,如 `iframe` / `Java` 服务器 等
5
+ - 对远端响应的数据格式也不严格限制(可以集中解析)
6
+ - 已在公司游戏前后端通信中应用,极大地降低了通信成本(简化调用)
7
+ - ts 类型严格
8
+
9
+ ## install
10
+ ```
11
+ pnpm i @zimi/remote
12
+ ```
13
+
14
+ ## examples
15
+
16
+ ### 调用示意
17
+
18
+ ```ts
19
+
20
+ // 远端
21
+ remote.register('something', async (params: Whatever) => {
22
+ return WhatYouWant
23
+ })
24
+
25
+ // 本地
26
+ // res === WhatYouWant
27
+ const res = await remote._.something(xxx)
28
+
29
+ ```
30
+
31
+ ### iframe 与父级相互调用
32
+
33
+ ```ts
34
+ // 1. 声明各自能提供的函数类型
35
+ // type.d.ts
36
+
37
+ // 父级能提供的函数
38
+ export type FuncsFromParent = {
39
+ plus: (data: [number, number]) => Promise<number>
40
+ }
41
+
42
+ // 子级能提供的函数
43
+ export type FuncsFromChild = {
44
+ multiply: (data: [number, number]) => Promise<number>
45
+ }
46
+ ```
47
+
48
+ ```ts
49
+ // 2. 父级 remote 初始化
50
+ // parent.ts
51
+
52
+ import { Remote, createIframeAdaptor } from '@zimi/remote'
53
+
54
+ function getOpWindow() {
55
+ return document.querySelector<HTMLIFrameElement>('#child-iframe')?.contentWindow
56
+ }
57
+
58
+ // 我们提供了生成 iframe adaptor 的工具函数
59
+ // 你也可以参考实现自己的 adaptor, 没多少代码
60
+ const adaptor = createIframeAdaptor({
61
+ onEmit: (data) => {
62
+ // 此处仅为示意,业务场景下应当限制对方的域名
63
+ getOpWindow()?.postMessage(data, '*')
64
+ },
65
+ })
66
+
67
+ const remote = new Remote<FuncsFromParent, FuncsFromChild>(adaptor, {
68
+ deviceId: 'parent',
69
+ })
70
+
71
+ // 父级注册自己能提供的函数
72
+ remote.register('plus', async ([a, b]) => a + b)
73
+ ```
74
+
75
+ ```ts
76
+ // 3. 子级 remote 初始化
77
+ // child-iframe.ts
78
+
79
+ import { Remote, createIframeAdaptor } from '@zimi/remote'
80
+
81
+ function getOpWindow() {
82
+ return window.top
83
+ }
84
+
85
+ // 我们提供了生成 iframe adaptor 的工具函数
86
+ // 你也可以参考实现自己的 adaptor, 没多少代码
87
+ const adaptor = createIframeAdaptor({
88
+ onEmit: (data) => {
89
+ // 此处仅为示意,业务场景下应当限制对方的域名
90
+ getOpWindow()?.postMessage(data, '*')
91
+ },
92
+ })
93
+
94
+ const remote = new Remote<FuncsFromChild, FuncsFromParent>(adaptor, {
95
+ // 当涉及到多子级时,可以通过该 deviceId 来区分彼此,
96
+ // 达到与不同子级通信的效果
97
+ deviceId: 'child',
98
+ })
99
+
100
+ // 子级注册自己能提供的函数
101
+ remote.register('multiply', async ([a, b]) => a * b)
102
+ ```
103
+
104
+ ```ts
105
+ // 好了,现在你可以父子间随意通信了
106
+
107
+ // 对方所有函数都被代理到 remote._.xxx 上了
108
+
109
+ // parent.ts
110
+ // 父级中可以直接调用子级的函数
111
+ // 有严格的类型与提示
112
+ await remote._.multiply([3, 2])
113
+
114
+ await remote._.multiply([3, 2], {
115
+ // 每个函数可以单独指定超时时间,超时后会抛出 RemoteTimeoutError
116
+ timeoutMs: 1000,
117
+ // 每个函数可以指定调用特定目标所有的函数(需要在 adaptor onEmit 中根据 targetDeviceId 往不同设备发送消息)
118
+ targetDeviceId: 'child-2'
119
+ })
120
+
121
+ // 调用对方未注册的函数,会抛出 RemoteNotFoundError
122
+ await remote._.notRegisteredFunc()
123
+
124
+ // 当对方函数发生运行时错误时,会抛出 RemoteError
125
+ // 以上所有 error 都继承自 Error
126
+ ```
127
+
128
+ ### 浏览器与服务器通信
129
+
130
+ ```ts
131
+ // 对方怎么写我们就不管了,假设对方返回的数据格式为:
132
+ interface JavaResponse {
133
+ // 假设 code >= 300 为错误;code < 300 为成功
134
+ code: number
135
+ // 响应的数据
136
+ data: unknown
137
+ // 可能存在的错误信息
138
+ errorMsg?: string
139
+ }
140
+ ```
141
+
142
+ 此时 adaptor 的 onEmit 函数稍微有些复杂,它需要解析服务端响应的数据,并封装为我们需要的格式:
143
+
144
+ ```ts
145
+ const adaptor = createHttpAdaptor({
146
+ onEmit: async (data) => {
147
+ // 这里只是简单示意,使用者可以根据自己的情况构造 request
148
+ const res = await fetch(`https://xxx.com/api/${data.name}`, {
149
+ method: 'POST',
150
+ body: JSON.stringify(data.data),
151
+ headers: {
152
+ 'Content-Type': 'application/json',
153
+ },
154
+ })
155
+ // 下面相当于我们代 server 端封装了一下数据
156
+ // callbackName 是一定会有的,此处只是为了类型安全
157
+ const callbackName = data.callbackName ?? 'IMPOSSIBLE_NO_CALLBACK_NAME'
158
+ const adaptorData: AdaptorPackageData = {
159
+ // 由于我们代 server 抛出事件
160
+ // 所以这里的 deviceId 和 targetDeviceId 是相反的
161
+ deviceId: data.targetDeviceId,
162
+ targetDeviceId: data.deviceId,
163
+ name: callbackName,
164
+ // 我们在下面根据不同情况来填充 data
165
+ data: null,
166
+ }
167
+ if (!res.ok) {
168
+ adaptorData.data = response.error(new RemoteError('network error'))
169
+ } else {
170
+ const json = (await res.json()) as {
171
+ code: number
172
+ data: unknown
173
+ errorMsg?: string
174
+ }
175
+ if (json.code < 300) {
176
+ adaptorData.data = response.success(json.data)
177
+ } else {
178
+ const error = new RemoteError(`server error: ${json.errorMsg}`)
179
+ // RemoteError 也接受 code, 你可以把服务端响应的错误码挂到其上,便于业务上区分处理
180
+ error.code = json.code
181
+ adaptorData.data = response.error(error)
182
+ }
183
+ }
184
+ // 一定要抛出 every 事件,remote 包基于此处理远端的响应
185
+ remoteEventManager.emit(remoteEventManager.EVERY_EVENT_NAME, adaptorData)
186
+ remoteEventManager.emit(callbackName, adaptorData)
187
+ },
188
+ })
189
+
190
+ // 由于服务端不会调用我们,所以我们无需提供函数,自然也无需调用 remote.register 注册函数
191
+ const remote = new Remote<{}, FuncsFromHttp>(adaptor, {
192
+ deviceId: 'client',
193
+ })
194
+
195
+ // 使用方法同前
196
+ await remote._.xxx(anyData)
197
+ ```
198
+
199
+ ### electron 的 main 和 renderer 之间通信
200
+
201
+ 使用比较复杂,暂无示例代码。
202
+
203
+ ### 与其他端通信(如 websocket)略
204
+
205
+ 你可以看看 `iframe adaptor` / `http adaptor` 源码,包含空行也就 30 行,依葫芦画瓢很轻易就能写一个。
206
+
207
+ ## 与 rpc 相比的优势
208
+
209
+ - 不局限于与服务端的通信,无论对方是任何端,只要能与 js 通信,就能使用该包;
210
+ - 相互通信,不存在“主从”的概念,通信双方是平等的;
211
+ - 类型严格;
212
+ - 包较底层,对项目整体的侵入较小,几乎不限制对方的响应的数据格式(因为可以自由解析对方的响应,即自由 emit);
213
+
214
+ ## 协议
215
+
216
+ > 由于通信双方是平等的,所以 B 调用 A 的流程也是一样的
217
+
218
+ ![protocol.png](https://cdn.16px.cc/public/2024-12-08/fm5up4GM9UCz.png?r=1682x836)
219
+
220
+ ---
221
+ ---
222
+
223
+ ## remote value
224
+
225
+ > 像使用 local value 一样使用 remote value
226
+
227
+ **首先强调一遍,这个包是一个通信的包,不负责权限控制,需要使用者在业务层面进行恰当的处理,控制好什么该暴露,什么不该暴露。**
228
+
229
+ 如果我们(暂且称为 端)需要访问其他端(可能是服务器,也可能是其他 iframe or whatever,暂且称为 乙 端)的一些数据/对象/方法,我们通常是这么做的:
230
+
231
+ //
232
+ ```ts
233
+ const obj = {
234
+ value: number,
235
+ setValue: (newValue: number) => {
236
+ obj.value = newValue
237
+ }
238
+ }
239
+
240
+ register({
241
+ method: 'get',
242
+ name: 'getValue',
243
+ handler: () => {
244
+ return obj.value
245
+ }
246
+ })
247
+
248
+ register({
249
+ method: 'post',
250
+ name: 'setValue',
251
+ handler: (newValue: string) => {
252
+ obj.setValue(newValue)
253
+ }
254
+ })
255
+ ```
256
+
257
+ // 甲 端
258
+ ```ts
259
+ const value = await fetch.get('getValue')
260
+ await fetch.post('setValue', newValue)
261
+ ```
262
+
263
+ 有没有感觉在重复劳动?我 乙 端已经有了一个现成的`obj`,可是我还是需要在`register`处“封装”一下。
264
+
265
+ ---
266
+
267
+ 那么,**下面这个**怎么样:
268
+
269
+ // 乙 端
270
+ ```ts
271
+ exposeToRemote(obj, config)
272
+ ```
273
+
274
+ //
275
+ ```ts
276
+ import { type obj } from '/path/to/obj'
277
+
278
+ const remoteObj = remoteValue<typeof obj>(config)
279
+
280
+ // 很遗憾没能实现 await remoteObj.value 直接取值,
281
+ // 无论是值还是函数,必须在后面加一个括号调用,才能取到结果。
282
+ const value = await remoteObj.value()
283
+ const subValue = await remoteObj.deep.path.to.value()
284
+
285
+ await remoteObj.setValue(newValue)
286
+ await remoteObj.deep.path.to.func(...params)
287
+ ```
288
+
289
+ **再次强调一遍,这个包是一个通信的包,不负责权限控制,需要使用者在业务层面进行恰当的处理,控制好什么该暴露,什么不该暴露。**
@@ -0,0 +1,33 @@
1
+ interface AdaptorPackageData<D = unknown> {
2
+ /**
3
+ * 自身设备 id,应确保唯一性(对方能凭借该 deviceId 找到该设备)
4
+ */
5
+ deviceId: string;
6
+ /**
7
+ * 对方的设备 id
8
+ */
9
+ targetDeviceId: string;
10
+ /**
11
+ * 远程调用的对方的方法名
12
+ */
13
+ name: string;
14
+ data: D;
15
+ /**
16
+ * 所需回调的方法名(如果需要回调的话)
17
+ */
18
+ callbackName?: string;
19
+ }
20
+ type Func<D = unknown, R = unknown> = (data: D) => R;
21
+ type AdaptorCallback = Func<AdaptorPackageData, void>;
22
+ 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
+
33
+ export type { Adaptor, AdaptorCallback, AdaptorPackageData };