@zimi/remote 0.1.0 → 0.1.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
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
+ }
17
+
18
+ function geneProxy<T extends object>(paths: string[], props: RemoteValueProps) {
19
+ return new Proxy<ToFunc<T>>(noop as unknown as ToFunc<T>, {
20
+ apply(target, thisArg, argArray) {
21
+ const { adaptor, globalName, timeoutMs = 30000, ...restProps } = props
22
+ const randomStr = Math.random().toString(36).slice(2)
23
+ const name = `__REMOTE_VALUE_REQ__${globalName}`
24
+ const responseName = `__REMOTE_VALUE_RES__${[globalName, ...paths].join('.')}-${randomStr}`
25
+
26
+ return new Promise((resolve, reject) => {
27
+ let timer: NodeJS.Timeout | undefined
28
+ const callback: AdaptorCallback = (e) => {
29
+ clearTimeout(timer)
30
+ if (RemoteError.isRemoteError(e.data)) {
31
+ reject(RemoteError.fromError(e.data))
32
+ } else {
33
+ resolve((e.data as ReturnType<typeof response.success>)?.data)
34
+ }
35
+ }
36
+ adaptor.once(responseName, callback)
37
+ timer = setTimeout(() => {
38
+ adaptor.off(responseName, callback)
39
+ reject(
40
+ new RemoteTimeoutError(
41
+ `timeout: ${[globalName, ...paths].join('.')}`
42
+ )
43
+ )
44
+ }, timeoutMs)
45
+ adaptor.emit({
46
+ name,
47
+ callbackName: responseName,
48
+ ...restProps,
49
+ data: {
50
+ paths,
51
+ args: argArray,
52
+ },
53
+ })
54
+ })
55
+ },
56
+ get(target, prop) {
57
+ return geneProxy([...paths, prop as string], props)
58
+ },
59
+ })
60
+ }
61
+
62
+ /**
63
+ * @example
64
+ * ```
65
+ * const obj = remoteValue<{
66
+ * a: number;
67
+ * b: {
68
+ * c: string;
69
+ * };
70
+ * funcD: () => number
71
+ * }>()
72
+ *
73
+ * const val_1 = await obj.a() // number
74
+ * const val_2 = await obj.b() // { c: string }
75
+ * const val_3 = await obj.b.c() // string
76
+ * const val_4 = await obj.funcD() // number
77
+ * ```
78
+ */
79
+ export function remoteValue<T extends object>(props: RemoteValueProps) {
80
+ return geneProxy<T>([], props)
81
+ }
82
+
83
+ interface ExposeProps {
84
+ globalName: string
85
+ adaptor: Adaptor
86
+ /**
87
+ * ['*'] or ['device_id_1', 'device_id_2']
88
+ */
89
+ exposeTo: string[]
90
+ /**
91
+ * 你可以在该回调中抛错,以阻止远程调用
92
+ */
93
+ onRequest?: (e: AdaptorPackageData) => void | Promise<void>
94
+ }
95
+
96
+ function defaultOnRequest(e: AdaptorPackageData) {
97
+ return e
98
+ }
99
+
100
+ export function exposeToRemote<T extends object>(obj: T, options: ExposeProps) {
101
+ const {
102
+ globalName,
103
+ adaptor,
104
+ exposeTo,
105
+ onRequest = defaultOnRequest,
106
+ } = options
107
+ const callback = async (e: AdaptorPackageData) => {
108
+ try {
109
+ await onRequest(e)
110
+ if (!exposeTo.includes(e.deviceId) && !exposeTo.includes('*')) {
111
+ throw new RemoteError('permission denied')
112
+ }
113
+ const { paths, args } = e.data as { paths: string[]; args: unknown[] }
114
+ let target = obj
115
+ for (let i = 0; i < paths.length - 1; i += 1) {
116
+ target = target[paths[i] as keyof typeof target] as typeof target
117
+ }
118
+ let res: unknown
119
+ if (target instanceof Function) {
120
+ res = await target(...args)
121
+ } else {
122
+ res = target
123
+ }
124
+ adaptor.emit({
125
+ name: e.callbackName ?? 'IMPOSSIBLE_NO_CALLBACK_NAME',
126
+ deviceId: e.targetDeviceId,
127
+ targetDeviceId: e.deviceId,
128
+ data: response.success(res),
129
+ })
130
+ } catch (error) {
131
+ adaptor.emit({
132
+ name: e.callbackName ?? 'IMPOSSIBLE_NO_CALLBACK_NAME',
133
+ deviceId: e.targetDeviceId,
134
+ targetDeviceId: e.deviceId,
135
+ data: response.error(RemoteError.fromError(error)),
136
+ })
137
+ }
138
+ }
139
+ adaptor.on(`__REMOTE_VALUE_REQ__${globalName}`, callback)
140
+ return () => {
141
+ adaptor.off(`__REMOTE_VALUE_REQ__${globalName}`, callback)
142
+ }
143
+ }
@@ -0,0 +1,113 @@
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
+ type Assert<T extends true> = T
24
+
25
+ interface TestObj {
26
+ a1: number
27
+ a2: number[]
28
+ a3: [number, string]
29
+ a4: {
30
+ b: number
31
+ c: [string]
32
+ }
33
+ a5: () => Promise<number>
34
+ a6: () => Promise<number[]>
35
+ a7: () => Promise<[number, string]>
36
+ b: {
37
+ b1: number
38
+ b2: number[]
39
+ b3: [number, string]
40
+ b4: () => {
41
+ b: number
42
+ c: [string]
43
+ }
44
+ b5: () => Promise<number>
45
+ b6: () => Promise<number[]>
46
+ b7: () => Promise<[number, string]>
47
+ }
48
+ f: {
49
+ b: number
50
+ c: {
51
+ d: () => number
52
+ }
53
+ }
54
+ }
55
+
56
+ export type TestA1 = Assert<
57
+ ToFunc<TestObj>['a1'] extends () => Promise<number> ? true : false
58
+ >
59
+ export type TestA2 = Assert<
60
+ ToFunc<TestObj>['a2'] extends () => Promise<number[]> ? true : false
61
+ >
62
+ export type TestA3 = Assert<
63
+ ToFunc<TestObj>['a3'] extends () => Promise<[number, string]> ? true : false
64
+ >
65
+ export type TestA4 = Assert<
66
+ ToFunc<TestObj>['a4'] extends () => Promise<{
67
+ b: number
68
+ c: [string]
69
+ }>
70
+ ? true
71
+ : false
72
+ >
73
+ export type TestA5 = Assert<
74
+ ToFunc<TestObj>['a5'] extends () => Promise<number> ? true : false
75
+ >
76
+ export type TestA6 = Assert<
77
+ ToFunc<TestObj>['a6'] extends () => Promise<number[]> ? true : false
78
+ >
79
+ export type TestA7 = Assert<
80
+ ToFunc<TestObj>['a7'] extends () => Promise<[number, string]> ? true : false
81
+ >
82
+ export type TestB1 = Assert<
83
+ ToFunc<TestObj>['b']['b1'] extends () => Promise<number> ? true : false
84
+ >
85
+ export type TestB2 = Assert<
86
+ ToFunc<TestObj>['b']['b2'] extends () => Promise<number[]> ? true : false
87
+ >
88
+ export type TestB3 = Assert<
89
+ ToFunc<TestObj>['b']['b3'] extends () => Promise<[number, string]> ? true : false
90
+ >
91
+ export type TestB4 = Assert<
92
+ ToFunc<TestObj>['b']['b4'] extends () => Promise<{
93
+ b: number
94
+ c: [string]
95
+ }>
96
+ ? true
97
+ : false
98
+ >
99
+ export type TestB5 = Assert<
100
+ ToFunc<TestObj>['b']['b5'] extends () => Promise<number> ? true : false
101
+ >
102
+ export type TestB6 = Assert<
103
+ ToFunc<TestObj>['b']['b6'] extends () => Promise<number[]> ? true : false
104
+ >
105
+ export type TestB7 = Assert<
106
+ ToFunc<TestObj>['b']['b7'] extends () => Promise<[number, string]> ? true : false
107
+ >
108
+ export type TestF = Assert<
109
+ ToFunc<TestObj>['f']['b'] extends () => Promise<number> ? true : false
110
+ >
111
+ export type TestF2 = Assert<
112
+ ToFunc<TestObj>['f']['c']['d'] extends () => Promise<number> ? true : false
113
+ >