@rn-org/react-native-thread 0.4.0 → 0.6.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/src/index.tsx CHANGED
@@ -8,12 +8,18 @@ export type ThreadInfo = {
8
8
  readonly name: string;
9
9
  };
10
10
 
11
+ export type ThreadError = {
12
+ message: string;
13
+ stack: string;
14
+ };
15
+
11
16
  export type ThreadHandle = {
12
17
  readonly id: number;
13
18
  readonly name: string;
14
19
  run(task: ThreadTask, params?: unknown): void;
15
20
  onMessage(handler: (data: unknown) => void): () => void;
16
21
  onMessage(): Promise<unknown>;
22
+ onError(handler: (error: ThreadError) => void): () => void;
17
23
  destroy(): void;
18
24
  };
19
25
 
@@ -22,6 +28,13 @@ const RN_THREAD_MESSAGE_EVENT = 'RNThreadMessage';
22
28
 
23
29
  const _registry = new Map<number, ThreadInfo>();
24
30
 
31
+ function _findByName(name: string): ThreadInfo | undefined {
32
+ for (const info of _registry.values()) {
33
+ if (info.name === name) return info;
34
+ }
35
+ return undefined;
36
+ }
37
+
25
38
  function _register(id: number, name: string): void {
26
39
  _registry.set(id, { id, name });
27
40
  }
@@ -30,18 +43,60 @@ function _unregister(id: number): void {
30
43
  _registry.delete(id);
31
44
  }
32
45
 
46
+ function _isBytecode(src: string): boolean {
47
+ return (
48
+ src.includes('[bytecode]') ||
49
+ /^\s*function\s*\(\)\s*\{\s*bytecode\s*\}/.test(src)
50
+ );
51
+ }
52
+
53
+ function _serializeValue(value: unknown): string {
54
+ if (value === undefined) return 'undefined';
55
+ if (value === null) return 'null';
56
+ if (typeof value === 'function') {
57
+ const src = value.toString();
58
+ if (_isBytecode(src)) {
59
+ if (__DEV__) {
60
+ console.warn(
61
+ '[react-native-thread] A function param returned a Hermes bytecode placeholder. ' +
62
+ "Add the Babel plugin: plugins: ['@rn-org/react-native-thread/babel-plugin']"
63
+ );
64
+ }
65
+ return 'undefined';
66
+ }
67
+ return src;
68
+ }
69
+ if (typeof value === 'string') return JSON.stringify(value);
70
+ if (typeof value === 'number' || typeof value === 'boolean')
71
+ return String(value);
72
+ if (Array.isArray(value))
73
+ return '[' + value.map(_serializeValue).join(',') + ']';
74
+ if (typeof value === 'object') {
75
+ const rec = value as Record<string, unknown>;
76
+ if (typeof rec.__rnThreadFn === 'string') {
77
+ return rec.__rnThreadFn;
78
+ }
79
+ const entries = Object.entries(rec)
80
+ .map(([k, v]) => `${JSON.stringify(k)}:${_serializeValue(v)}`)
81
+ .join(',');
82
+ return '{' + entries + '}';
83
+ }
84
+ return String(value);
85
+ }
86
+
87
+ function _wrapWithErrorHandler(code: string): string {
88
+ return `try { ${code} } catch(__e__) { resolveThreadMessage(JSON.stringify({ __rnThreadError: true, message: __e__.message || String(__e__), stack: __e__.stack || '' })); }`;
89
+ }
90
+
33
91
  function toCode(task: ThreadTask, params?: unknown): string {
34
92
  if (typeof task === 'string') {
35
- const paramsJson =
36
- params !== undefined ? JSON.stringify(params) : 'undefined';
37
- return `var __params__ = ${paramsJson};\n${task}`;
93
+ const paramsCode =
94
+ params !== undefined ? _serializeValue(params) : 'undefined';
95
+ return _wrapWithErrorHandler(`var __params__ = ${paramsCode};\n${task}`);
38
96
  }
39
97
 
40
98
  const src = task.toString();
41
- if (
42
- src.includes('[bytecode]') ||
43
- /^\s*function\s*\(\)\s*\{\s*bytecode\s*\}/.test(src)
44
- ) {
99
+ if (_isBytecode(src)) {
45
100
  if (__DEV__) {
46
101
  console.warn(
47
102
  '[react-native-thread] fn.toString() returned a Hermes bytecode placeholder. ' +
@@ -51,8 +106,8 @@ function toCode(task: ThreadTask, params?: unknown): string {
51
106
  return '/* bytecode: no-op */';
52
107
  }
53
108
 
54
- const argsStr = params !== undefined ? JSON.stringify(params) : '';
55
- return `(${src})(${argsStr})`;
109
+ const argsStr = params !== undefined ? _serializeValue(params) : '';
110
+ return _wrapWithErrorHandler(`(${src})(${argsStr})`);
56
111
  }
57
112
 
58
113
  const SHARED_THREAD_NAME = 'RNOrgThread';
@@ -75,6 +130,12 @@ export function runOnNewJS(
75
130
  params?: unknown,
76
131
  name?: string
77
132
  ): ThreadHandle {
133
+ if (name != null) {
134
+ const existing = _findByName(name);
135
+ if (existing) {
136
+ return createThreadHandle(existing.id, existing.name);
137
+ }
138
+ }
78
139
  const id = ReactNativeThread.createThread();
79
140
  const resolvedName = name ?? `RNThread-${id}`;
80
141
  _register(id, resolvedName);
@@ -83,6 +144,12 @@ export function runOnNewJS(
83
144
  }
84
145
 
85
146
  export function createThread(name?: string): ThreadHandle {
147
+ if (name != null) {
148
+ const existing = _findByName(name);
149
+ if (existing) {
150
+ return createThreadHandle(existing.id, existing.name);
151
+ }
152
+ }
86
153
  const id = ReactNativeThread.createThread();
87
154
  const resolvedName = name ?? `RNThread-${id}`;
88
155
  _register(id, resolvedName);
@@ -151,6 +218,16 @@ export function onMessage(
151
218
  });
152
219
  }
153
220
 
221
+ function _isThreadError(
222
+ data: unknown
223
+ ): data is { __rnThreadError: true; message: string; stack: string } {
224
+ return (
225
+ typeof data === 'object' &&
226
+ data !== null &&
227
+ (data as any).__rnThreadError === true
228
+ );
229
+ }
230
+
154
231
  function createThreadHandle(id: number, name: string): ThreadHandle {
155
232
  return {
156
233
  id,
@@ -161,18 +238,31 @@ function createThreadHandle(id: number, name: string): ThreadHandle {
161
238
  onMessage: function (handler?: (data: unknown) => void) {
162
239
  if (handler) {
163
240
  return onMessage((data, threadId) => {
164
- if (threadId === id) handler(data);
241
+ if (threadId === id && !_isThreadError(data)) handler(data);
165
242
  });
166
243
  }
167
- return new Promise<unknown>((resolve) => {
244
+ return new Promise<unknown>((resolve, reject) => {
168
245
  const unsub = onMessage((data, threadId) => {
169
246
  if (threadId === id) {
170
247
  unsub();
171
- resolve(data);
248
+ if (_isThreadError(data)) {
249
+ const err = new Error(data.message);
250
+ err.stack = data.stack;
251
+ reject(err);
252
+ } else {
253
+ resolve(data);
254
+ }
172
255
  }
173
256
  });
174
257
  });
175
258
  } as ThreadHandle['onMessage'],
259
+ onError(handler: (error: ThreadError) => void) {
260
+ return onMessage((data, threadId) => {
261
+ if (threadId === id && _isThreadError(data)) {
262
+ handler({ message: data.message, stack: data.stack });
263
+ }
264
+ });
265
+ },
176
266
  destroy() {
177
267
  destroyThread(id);
178
268
  },