@react-devtools-plus/core 0.2.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/dist/index.js ADDED
@@ -0,0 +1,686 @@
1
+ // src/client/index.ts
2
+ import { isBrowser, target } from "@react-devtools-plus/shared";
3
+ function setDevToolsClientUrl(url) {
4
+ target.__REACT_DEVTOOLS_CLIENT_URL__ = url;
5
+ }
6
+ function getDevToolsClientUrl() {
7
+ var _a;
8
+ return (_a = target.__REACT_DEVTOOLS_CLIENT_URL__) != null ? _a : (() => {
9
+ if (isBrowser) {
10
+ const devtoolsMeta = document.querySelector("meta[name=__REACT_DEVTOOLS_CLIENT_URL__]");
11
+ if (devtoolsMeta)
12
+ return devtoolsMeta.getAttribute("content") || "";
13
+ }
14
+ return "";
15
+ })();
16
+ }
17
+ function isDevToolsEnabled() {
18
+ return target.__REACT_DEVTOOLS_ENABLED__ === true;
19
+ }
20
+ function setDevToolsEnabled(enabled) {
21
+ target.__REACT_DEVTOOLS_ENABLED__ = enabled;
22
+ }
23
+ function getDevToolsChannelName() {
24
+ return target.__REACT_DEVTOOLS_BROADCAST_CHANNEL__ || "react-devtools-plus";
25
+ }
26
+ function setDevToolsChannelName(channelName) {
27
+ target.__REACT_DEVTOOLS_BROADCAST_CHANNEL__ = channelName;
28
+ }
29
+ var ClientConnectionManager = class {
30
+ constructor(options = {}) {
31
+ this.connections = /* @__PURE__ */ new Map();
32
+ this.reconnectAttempts = /* @__PURE__ */ new Map();
33
+ this.reconnectTimers = /* @__PURE__ */ new Map();
34
+ this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
35
+ this.reconnectDelay = options.reconnectDelay || 1e3;
36
+ }
37
+ /**
38
+ * Register a connection
39
+ * 注册连接
40
+ */
41
+ register(id, connection) {
42
+ this.connections.set(id, connection);
43
+ this.reconnectAttempts.set(id, 0);
44
+ }
45
+ /**
46
+ * Unregister a connection
47
+ * 注销连接
48
+ */
49
+ unregister(id) {
50
+ const timer = this.reconnectTimers.get(id);
51
+ if (timer) {
52
+ clearTimeout(timer);
53
+ this.reconnectTimers.delete(id);
54
+ }
55
+ this.connections.delete(id);
56
+ this.reconnectAttempts.delete(id);
57
+ }
58
+ /**
59
+ * Get a connection
60
+ * 获取连接
61
+ */
62
+ get(id) {
63
+ return this.connections.get(id);
64
+ }
65
+ /**
66
+ * Get all connections
67
+ * 获取所有连接
68
+ */
69
+ getAll() {
70
+ return new Map(this.connections);
71
+ }
72
+ /**
73
+ * Check if a connection exists
74
+ * 检查连接是否存在
75
+ */
76
+ has(id) {
77
+ return this.connections.has(id);
78
+ }
79
+ /**
80
+ * Handle connection error and attempt reconnect
81
+ * 处理连接错误并尝试重连
82
+ */
83
+ handleError(id, error, reconnectFn) {
84
+ console.error(`[React DevTools] Connection error for ${id}:`, error);
85
+ if (!reconnectFn)
86
+ return;
87
+ const attempts = this.reconnectAttempts.get(id) || 0;
88
+ if (attempts >= this.maxReconnectAttempts) {
89
+ console.error(`[React DevTools] Max reconnect attempts reached for ${id}`);
90
+ return;
91
+ }
92
+ const delay = this.reconnectDelay * 2 ** attempts;
93
+ const timer = setTimeout(async () => {
94
+ try {
95
+ await reconnectFn();
96
+ this.reconnectAttempts.set(id, 0);
97
+ } catch (err) {
98
+ this.reconnectAttempts.set(id, attempts + 1);
99
+ this.handleError(id, err, reconnectFn);
100
+ }
101
+ }, delay);
102
+ this.reconnectTimers.set(id, timer);
103
+ }
104
+ /**
105
+ * Close all connections
106
+ * 关闭所有连接
107
+ */
108
+ closeAll() {
109
+ this.connections.forEach((connection) => {
110
+ if (connection.$close) {
111
+ connection.$close();
112
+ }
113
+ });
114
+ this.reconnectTimers.forEach((timer) => {
115
+ clearTimeout(timer);
116
+ });
117
+ this.connections.clear();
118
+ this.reconnectAttempts.clear();
119
+ this.reconnectTimers.clear();
120
+ }
121
+ };
122
+ var globalConnectionManager = new ClientConnectionManager();
123
+
124
+ // src/events.ts
125
+ var EventBus = class {
126
+ constructor() {
127
+ this.handlers = /* @__PURE__ */ new Map();
128
+ }
129
+ /**
130
+ * Subscribe to an event
131
+ * 订阅事件
132
+ */
133
+ on(type, handler) {
134
+ if (!this.handlers.has(type)) {
135
+ this.handlers.set(type, /* @__PURE__ */ new Set());
136
+ }
137
+ const handlers = this.handlers.get(type);
138
+ handlers.add(handler);
139
+ return () => {
140
+ handlers.delete(handler);
141
+ if (handlers.size === 0) {
142
+ this.handlers.delete(type);
143
+ }
144
+ };
145
+ }
146
+ /**
147
+ * Subscribe to an event once
148
+ * 订阅一次性事件
149
+ */
150
+ once(type, handler) {
151
+ let unsubscribe;
152
+ const wrappedHandler = (event) => {
153
+ handler(event);
154
+ unsubscribe();
155
+ };
156
+ unsubscribe = this.on(type, wrappedHandler);
157
+ return unsubscribe;
158
+ }
159
+ /**
160
+ * Emit an event
161
+ * 发送事件
162
+ */
163
+ emit(event) {
164
+ const handlers = this.handlers.get(event.type);
165
+ if (!handlers)
166
+ return;
167
+ handlers.forEach((handler) => {
168
+ try {
169
+ handler(event);
170
+ } catch (error) {
171
+ console.error(`[React DevTools] Error in event handler for "${event.type}":`, error);
172
+ }
173
+ });
174
+ }
175
+ /**
176
+ * Remove all event handlers
177
+ * 移除所有事件处理器
178
+ */
179
+ clear() {
180
+ this.handlers.clear();
181
+ }
182
+ /**
183
+ * Remove all handlers for a specific event type
184
+ * 移除特定事件类型的所有处理器
185
+ */
186
+ clearType(type) {
187
+ this.handlers.delete(type);
188
+ }
189
+ /**
190
+ * Get number of handlers for an event type
191
+ * 获取事件类型的处理器数量
192
+ */
193
+ getHandlerCount(type) {
194
+ var _a;
195
+ return ((_a = this.handlers.get(type)) == null ? void 0 : _a.size) || 0;
196
+ }
197
+ /**
198
+ * Check if there are any handlers for an event type
199
+ * 检查是否有事件类型的处理器
200
+ */
201
+ hasHandlers(type) {
202
+ return this.getHandlerCount(type) > 0;
203
+ }
204
+ };
205
+ var globalEventBus = new EventBus();
206
+
207
+ // src/plugin/manager.ts
208
+ var PluginManager = class {
209
+ constructor() {
210
+ this.plugins = /* @__PURE__ */ new Map();
211
+ this.contexts = /* @__PURE__ */ new Map();
212
+ this.eventBus = new EventBus();
213
+ this.rpcFunctions = /* @__PURE__ */ new Map();
214
+ }
215
+ /**
216
+ * Register a plugin
217
+ * 注册插件
218
+ */
219
+ async register(plugin) {
220
+ if (this.plugins.has(plugin.id)) {
221
+ throw new Error(`[React DevTools] Plugin "${plugin.id}" is already registered`);
222
+ }
223
+ const context = this.createContext(plugin.id);
224
+ this.contexts.set(plugin.id, context);
225
+ this.plugins.set(plugin.id, plugin);
226
+ if (plugin.rpc) {
227
+ Object.entries(plugin.rpc).forEach(([name, fn]) => {
228
+ const fullName = `${plugin.id}.${name}`;
229
+ this.rpcFunctions.set(fullName, fn);
230
+ });
231
+ }
232
+ if (plugin.on) {
233
+ Object.entries(plugin.on).forEach(([type, handler]) => {
234
+ if (handler) {
235
+ this.eventBus.on(type, handler);
236
+ }
237
+ });
238
+ }
239
+ if (plugin.setup) {
240
+ try {
241
+ await plugin.setup(context);
242
+ console.log(`[React DevTools] Plugin "${plugin.name}" registered successfully`);
243
+ } catch (error) {
244
+ console.error(`[React DevTools] Failed to setup plugin "${plugin.name}":`, error);
245
+ this.plugins.delete(plugin.id);
246
+ this.contexts.delete(plugin.id);
247
+ throw error;
248
+ }
249
+ }
250
+ }
251
+ /**
252
+ * Unregister a plugin
253
+ * 注销插件
254
+ */
255
+ async unregister(pluginId) {
256
+ const plugin = this.plugins.get(pluginId);
257
+ if (!plugin) {
258
+ console.warn(`[React DevTools] Plugin "${pluginId}" is not registered`);
259
+ return;
260
+ }
261
+ if (plugin.teardown) {
262
+ try {
263
+ await plugin.teardown();
264
+ } catch (error) {
265
+ console.error(`[React DevTools] Error during plugin teardown for "${plugin.name}":`, error);
266
+ }
267
+ }
268
+ if (plugin.rpc) {
269
+ Object.keys(plugin.rpc).forEach((name) => {
270
+ const fullName = `${pluginId}.${name}`;
271
+ this.rpcFunctions.delete(fullName);
272
+ });
273
+ }
274
+ this.plugins.delete(pluginId);
275
+ this.contexts.delete(pluginId);
276
+ console.log(`[React DevTools] Plugin "${plugin.name}" unregistered`);
277
+ }
278
+ /**
279
+ * Get a plugin by ID
280
+ * 根据 ID 获取插件
281
+ */
282
+ get(pluginId) {
283
+ return this.plugins.get(pluginId);
284
+ }
285
+ /**
286
+ * Get all plugins
287
+ * 获取所有插件
288
+ */
289
+ getAll() {
290
+ return Array.from(this.plugins.values());
291
+ }
292
+ /**
293
+ * Check if a plugin is registered
294
+ * 检查插件是否已注册
295
+ */
296
+ has(pluginId) {
297
+ return this.plugins.has(pluginId);
298
+ }
299
+ /**
300
+ * Get plugin context
301
+ * 获取插件上下文
302
+ */
303
+ getContext(pluginId) {
304
+ return this.contexts.get(pluginId);
305
+ }
306
+ /**
307
+ * Set component tree getter
308
+ * 设置组件树获取器
309
+ */
310
+ setComponentTreeGetter(getter) {
311
+ this.componentTreeGetter = getter;
312
+ }
313
+ /**
314
+ * Set component details getter
315
+ * 设置组件详情获取器
316
+ */
317
+ setComponentDetailsGetter(getter) {
318
+ this.componentDetailsGetter = getter;
319
+ }
320
+ /**
321
+ * Call plugin RPC function
322
+ * 调用插件 RPC 函数
323
+ */
324
+ async callRPC(name, ...args) {
325
+ const fn = this.rpcFunctions.get(name);
326
+ if (!fn) {
327
+ throw new Error(`[React DevTools] RPC function "${name}" not found`);
328
+ }
329
+ try {
330
+ return await fn(...args);
331
+ } catch (error) {
332
+ console.error(`[React DevTools] Error calling RPC function "${name}":`, error);
333
+ throw error;
334
+ }
335
+ }
336
+ /**
337
+ * Emit event to all plugins
338
+ * 向所有插件发送事件
339
+ */
340
+ emit(event) {
341
+ this.eventBus.emit(event);
342
+ }
343
+ /**
344
+ * Subscribe to events
345
+ * 订阅事件
346
+ */
347
+ on(type, handler) {
348
+ return this.eventBus.on(type, handler);
349
+ }
350
+ /**
351
+ * Cleanup all plugins
352
+ * 清理所有插件
353
+ */
354
+ async cleanup() {
355
+ const pluginIds = Array.from(this.plugins.keys());
356
+ for (const pluginId of pluginIds) {
357
+ await this.unregister(pluginId);
358
+ }
359
+ this.eventBus.clear();
360
+ this.rpcFunctions.clear();
361
+ }
362
+ /**
363
+ * Create plugin context
364
+ * 创建插件上下文
365
+ */
366
+ createContext(pluginId) {
367
+ return {
368
+ getComponentTree: async () => {
369
+ if (!this.componentTreeGetter) {
370
+ throw new Error("[React DevTools] Component tree getter not set");
371
+ }
372
+ return this.componentTreeGetter();
373
+ },
374
+ getComponentDetails: async (componentId) => {
375
+ if (!this.componentDetailsGetter) {
376
+ throw new Error("[React DevTools] Component details getter not set");
377
+ }
378
+ return this.componentDetailsGetter(componentId);
379
+ },
380
+ emit: (event) => {
381
+ this.eventBus.emit(event);
382
+ },
383
+ on: (type, handler) => {
384
+ return this.eventBus.on(type, handler);
385
+ },
386
+ registerRPC: (name, fn) => {
387
+ const fullName = `${pluginId}.${name}`;
388
+ this.rpcFunctions.set(fullName, fn);
389
+ },
390
+ callRPC: async (name, ...args) => {
391
+ return this.callRPC(name, ...args);
392
+ }
393
+ };
394
+ }
395
+ };
396
+ var globalPluginManager = new PluginManager();
397
+
398
+ // ../../node_modules/.pnpm/birpc@0.2.19/node_modules/birpc/dist/index.mjs
399
+ var DEFAULT_TIMEOUT = 6e4;
400
+ function defaultSerialize(i) {
401
+ return i;
402
+ }
403
+ var defaultDeserialize = defaultSerialize;
404
+ var { clearTimeout: clearTimeout2, setTimeout: setTimeout2 } = globalThis;
405
+ var random = Math.random.bind(Math);
406
+ function createBirpc(functions, options) {
407
+ const {
408
+ post,
409
+ on,
410
+ off = () => {
411
+ },
412
+ eventNames = [],
413
+ serialize = defaultSerialize,
414
+ deserialize = defaultDeserialize,
415
+ resolver,
416
+ bind = "rpc",
417
+ timeout = DEFAULT_TIMEOUT
418
+ } = options;
419
+ const rpcPromiseMap = /* @__PURE__ */ new Map();
420
+ let _promise;
421
+ let closed = false;
422
+ const rpc = new Proxy({}, {
423
+ get(_, method) {
424
+ if (method === "$functions")
425
+ return functions;
426
+ if (method === "$close")
427
+ return close;
428
+ if (method === "then" && !eventNames.includes("then") && !("then" in functions))
429
+ return void 0;
430
+ const sendEvent = (...args) => {
431
+ post(serialize({ m: method, a: args, t: "q" }));
432
+ };
433
+ if (eventNames.includes(method)) {
434
+ sendEvent.asEvent = sendEvent;
435
+ return sendEvent;
436
+ }
437
+ const sendCall = async (...args) => {
438
+ if (closed)
439
+ throw new Error(`[birpc] rpc is closed, cannot call "${method}"`);
440
+ if (_promise) {
441
+ try {
442
+ await _promise;
443
+ } finally {
444
+ _promise = void 0;
445
+ }
446
+ }
447
+ return new Promise((resolve, reject) => {
448
+ var _a;
449
+ const id = nanoid();
450
+ let timeoutId;
451
+ if (timeout >= 0) {
452
+ timeoutId = setTimeout2(() => {
453
+ var _a2;
454
+ try {
455
+ (_a2 = options.onTimeoutError) == null ? void 0 : _a2.call(options, method, args);
456
+ throw new Error(`[birpc] timeout on calling "${method}"`);
457
+ } catch (e) {
458
+ reject(e);
459
+ }
460
+ rpcPromiseMap.delete(id);
461
+ }, timeout);
462
+ if (typeof timeoutId === "object")
463
+ timeoutId = (_a = timeoutId.unref) == null ? void 0 : _a.call(timeoutId);
464
+ }
465
+ rpcPromiseMap.set(id, { resolve, reject, timeoutId, method });
466
+ post(serialize({ m: method, a: args, i: id, t: "q" }));
467
+ });
468
+ };
469
+ sendCall.asEvent = sendEvent;
470
+ return sendCall;
471
+ }
472
+ });
473
+ function close() {
474
+ closed = true;
475
+ rpcPromiseMap.forEach(({ reject, method }) => {
476
+ reject(new Error(`[birpc] rpc is closed, cannot call "${method}"`));
477
+ });
478
+ rpcPromiseMap.clear();
479
+ off(onMessage);
480
+ }
481
+ async function onMessage(data, ...extra) {
482
+ const msg = deserialize(data);
483
+ if (msg.t === "q") {
484
+ const { m: method, a: args } = msg;
485
+ let result, error;
486
+ const fn = resolver ? resolver(method, functions[method]) : functions[method];
487
+ if (!fn) {
488
+ error = new Error(`[birpc] function "${method}" not found`);
489
+ } else {
490
+ try {
491
+ result = await fn.apply(bind === "rpc" ? rpc : functions, args);
492
+ } catch (e) {
493
+ error = e;
494
+ }
495
+ }
496
+ if (msg.i) {
497
+ if (error && options.onError)
498
+ options.onError(error, method, args);
499
+ post(serialize({ t: "s", i: msg.i, r: result, e: error }), ...extra);
500
+ }
501
+ } else {
502
+ const { i: ack, r: result, e: error } = msg;
503
+ const promise = rpcPromiseMap.get(ack);
504
+ if (promise) {
505
+ clearTimeout2(promise.timeoutId);
506
+ if (error)
507
+ promise.reject(error);
508
+ else
509
+ promise.resolve(result);
510
+ }
511
+ rpcPromiseMap.delete(ack);
512
+ }
513
+ }
514
+ _promise = on(onMessage);
515
+ return rpc;
516
+ }
517
+ var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
518
+ function nanoid(size = 21) {
519
+ let id = "";
520
+ let i = size;
521
+ while (i--)
522
+ id += urlAlphabet[random() * 64 | 0];
523
+ return id;
524
+ }
525
+
526
+ // src/rpc/channel.ts
527
+ function createBroadcastChannel(channelName) {
528
+ const channel = new BroadcastChannel(channelName);
529
+ const handlers = /* @__PURE__ */ new Set();
530
+ channel.addEventListener("message", (event) => {
531
+ handlers.forEach((handler) => handler(event.data));
532
+ });
533
+ return {
534
+ send: (message) => {
535
+ channel.postMessage(message);
536
+ },
537
+ onMessage: (handler) => {
538
+ handlers.add(handler);
539
+ return () => handlers.delete(handler);
540
+ },
541
+ close: () => {
542
+ channel.close();
543
+ handlers.clear();
544
+ }
545
+ };
546
+ }
547
+ function createPostMessageChannel(target2, origin = "*") {
548
+ const handlers = /* @__PURE__ */ new Set();
549
+ const messageHandler = (event) => {
550
+ if (origin !== "*" && event.origin !== origin)
551
+ return;
552
+ handlers.forEach((handler) => handler(event.data));
553
+ };
554
+ window.addEventListener("message", messageHandler);
555
+ return {
556
+ send: (message) => {
557
+ target2.postMessage(message, origin);
558
+ },
559
+ onMessage: (handler) => {
560
+ handlers.add(handler);
561
+ return () => handlers.delete(handler);
562
+ },
563
+ close: () => {
564
+ window.removeEventListener("message", messageHandler);
565
+ handlers.clear();
566
+ }
567
+ };
568
+ }
569
+ function createWebSocketChannel(url) {
570
+ const ws = new WebSocket(url);
571
+ const handlers = /* @__PURE__ */ new Set();
572
+ ws.addEventListener("message", (event) => {
573
+ try {
574
+ const data = JSON.parse(event.data);
575
+ handlers.forEach((handler) => handler(data));
576
+ } catch (error) {
577
+ console.error("[RPC] Failed to parse WebSocket message:", error);
578
+ }
579
+ });
580
+ return {
581
+ send: (message) => {
582
+ if (ws.readyState === WebSocket.OPEN) {
583
+ ws.send(JSON.stringify(message));
584
+ }
585
+ },
586
+ onMessage: (handler) => {
587
+ handlers.add(handler);
588
+ return () => handlers.delete(handler);
589
+ },
590
+ close: () => {
591
+ ws.close();
592
+ handlers.clear();
593
+ }
594
+ };
595
+ }
596
+ function createCustomEventChannel(eventName) {
597
+ const handlers = /* @__PURE__ */ new Set();
598
+ const eventHandler = (event) => {
599
+ handlers.forEach((handler) => handler(event.detail));
600
+ };
601
+ window.addEventListener(eventName, eventHandler);
602
+ return {
603
+ send: (message) => {
604
+ window.dispatchEvent(new CustomEvent(eventName, { detail: message }));
605
+ },
606
+ onMessage: (handler) => {
607
+ handlers.add(handler);
608
+ return () => handlers.delete(handler);
609
+ },
610
+ close: () => {
611
+ window.removeEventListener(eventName, eventHandler);
612
+ handlers.clear();
613
+ }
614
+ };
615
+ }
616
+
617
+ // src/rpc/index.ts
618
+ function createClientRPC(functions, channel, options = {}) {
619
+ const { timeout = 3e4 } = options;
620
+ const rpc = createBirpc(functions, {
621
+ post: (data) => {
622
+ channel.send(data);
623
+ },
624
+ on: (fn) => {
625
+ return channel.onMessage(fn);
626
+ },
627
+ timeout
628
+ });
629
+ return Object.assign(rpc, functions, {
630
+ $functions: functions,
631
+ $channel: channel,
632
+ $state: {
633
+ state: "connected",
634
+ connectedAt: Date.now()
635
+ },
636
+ $close: () => {
637
+ var _a;
638
+ (_a = channel.close) == null ? void 0 : _a.call(channel);
639
+ }
640
+ });
641
+ }
642
+ function createServerRPC(functions, channel, options = {}) {
643
+ const { timeout = 3e4 } = options;
644
+ const rpc = createBirpc(functions, {
645
+ post: (data) => {
646
+ channel.send(data);
647
+ },
648
+ on: (fn) => {
649
+ return channel.onMessage(fn);
650
+ },
651
+ timeout
652
+ });
653
+ return Object.assign(rpc, functions, {
654
+ $functions: functions,
655
+ $channel: channel,
656
+ $state: {
657
+ state: "connected",
658
+ connectedAt: Date.now()
659
+ },
660
+ $close: () => {
661
+ var _a;
662
+ (_a = channel.close) == null ? void 0 : _a.call(channel);
663
+ }
664
+ });
665
+ }
666
+ export {
667
+ ClientConnectionManager,
668
+ EventBus,
669
+ PluginManager,
670
+ createBroadcastChannel,
671
+ createClientRPC,
672
+ createCustomEventChannel,
673
+ createPostMessageChannel,
674
+ createServerRPC,
675
+ createWebSocketChannel,
676
+ getDevToolsChannelName,
677
+ getDevToolsClientUrl,
678
+ globalConnectionManager,
679
+ globalEventBus,
680
+ globalPluginManager,
681
+ isDevToolsEnabled,
682
+ setDevToolsChannelName,
683
+ setDevToolsClientUrl,
684
+ setDevToolsEnabled
685
+ };
686
+ //# sourceMappingURL=index.js.map