dsh-mobile-plugin 0.5.3 → 0.5.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.
@@ -9,15 +9,17 @@
9
9
  * 1. 游离的 Promise 拒绝——Node 15+ 默认 throw 并杀进程,插件里大量 fire-and-forget;
10
10
  * 2. 第三方回调(事件流、WS、timer)里抛错,没有 try 包着就是 uncaughtException。
11
11
  *
12
- * 策略:全部记入内存账本并保持存活;只有 uncaughtException 计入熔断,且必须落在时间窗
13
- * 内连发到 `fatalCount` 才主动退出,交给 `Restart=on-failure` 拉起。`createGuard()` 是
14
- * 纯状态机,`installProcessGuard()` 才是进程接线,拆分是为了能被守卫测试覆盖。
12
+ * 策略:全部记入内存账本并保持存活,**不主动退出进程**。这个进程里跑的不止我们:无头宿主
13
+ * 把整份 profile 的插件都装在同一个 process 上,别人的崩溃循环也走同一条 `uncaughtException`。
14
+ * 在这里 exit 就等于「别人的插件崩五次,我们把用户的 DSH 关掉」,而桌面端根本没有守护来拉起。
15
+ * 所以只记账、只报警,生死交给部署方(systemd 的 Restart、宿主的 installFailLoud 只管装载期)。
16
+ *
17
+ * `createGuard()` 是纯状态机,`installProcessGuard()` 才是进程接线,拆分是为了能被守卫测试覆盖。
15
18
  */
16
19
 
17
20
  const FATAL_FAULT_COUNT = 5;
18
21
  const CONSECUTIVE_FAULT_WINDOW_MS = 60 * 1000;
19
22
  const FAULT_LEDGER_LIMIT = 50;
20
- const FATAL_EXIT_CODE = 1;
21
23
 
22
24
  /** 把任意 throw 出来的值压成一行可读文本。 */
23
25
  function describeError(err) {
@@ -38,7 +40,6 @@ function describeError(err) {
38
40
  * @param {number} [options.ledgerLimit] 账本上限,缺省 50
39
41
  * @param {() => number} [options.now] 注入时钟,便于测试
40
42
  * @param {(line: string) => void} [options.log] 注入日志,便于测试
41
- * @param {(code: number) => void} [options.exit] 注入退出,便于测试
42
43
  * @returns {{handleUncaughtException: Function, handleUnhandledRejection: Function,
43
44
  * recordFault: Function, faults: Function, stats: Function, clear: Function,
44
45
  * isFatal: Function}}
@@ -48,7 +49,6 @@ function createGuard(options = {}) {
48
49
  const windowMs = options.windowMs ?? CONSECUTIVE_FAULT_WINDOW_MS;
49
50
  const ledgerLimit = options.ledgerLimit ?? FAULT_LEDGER_LIMIT;
50
51
  const now = options.now ?? (() => Date.now());
51
- const exit = options.exit ?? ((code) => process.exit(code));
52
52
  const log = options.log ?? ((line) => { process.stderr.write(`${line}\n`); });
53
53
 
54
54
  /** @type {{at: number, kind: string, message: string, fatal: boolean}[]} */
@@ -65,8 +65,8 @@ function createGuard(options = {}) {
65
65
 
66
66
  return {
67
67
  /**
68
- * `process.on('uncaughtException')` 的处理函数。
69
- * 记一笔,计入连续计数,够数才退出。
68
+ * `process.on('uncaughtException')` 的处理函数。只记一笔并计入连续计数:够数**不退出**,
69
+ * 只把这条故障在账本里标成 fatal,让 `/api/relay/status` 的自检能说出「这台在崩溃循环」。
70
70
  */
71
71
  handleUncaughtException(err) {
72
72
  const at = now();
@@ -77,16 +77,15 @@ function createGuard(options = {}) {
77
77
  const fatal = consecutive >= fatalCount;
78
78
  if (fatal) fatalEver = true;
79
79
  record('uncaughtException', describeError(err), fatal);
80
- log(`[dsh-mobile-plugin] 未捕获异常(连续 ${consecutive}/${fatalCount}${fatal ? ',判定崩溃循环,将退出' : ''}): ${describeError(err)}`);
81
- if (fatal) exit(FATAL_EXIT_CODE);
80
+ log(`[dsh-mobile-plugin] 未捕获异常(连续 ${consecutive}/${fatalCount}${fatal ? ',判定崩溃循环;不退出进程,请查宿主日志' : ''}): ${describeError(err)}`);
82
81
  return fatal;
83
82
  },
84
83
 
85
84
  /**
86
85
  * `process.on('unhandledRejection')` 的处理函数。
87
86
  *
88
- * 刻意**不计入熔断**:游离 reject 在长驻服务里非常常见(一次后台推送失败就
89
- * 一个),把它们累加成退出条件会制造新的崩溃源。挂上处理器本身就已经把
87
+ * 刻意**不计入连续计数**:游离 reject 在长驻服务里非常常见(一次后台推送失败就
88
+ * 一个),把它们算进崩溃循环的判据会制造新的误报。挂上处理器本身就已经把
90
89
  * Node 的默认「throw 并杀进程」改成了「记录并继续」。
91
90
  */
92
91
  handleUnhandledRejection(reason) {
@@ -196,5 +195,4 @@ module.exports = {
196
195
  FATAL_FAULT_COUNT,
197
196
  CONSECUTIVE_FAULT_WINDOW_MS,
198
197
  FAULT_LEDGER_LIMIT,
199
- FATAL_EXIT_CODE,
200
198
  };
package/lib/impl.js CHANGED
@@ -51,37 +51,27 @@ const { activeGuard } = require('./host-guard.cjs')
51
51
  // 有一个落盘处:客户端半边(lib/client.js)上报 → 这里 → 移动端读取。
52
52
 
53
53
  export const name = 'dsh-mobile-plugin'
54
- // The mobile API runs headless inside the Cordis Host. `webServer` and
55
- // `sessions` are the services it needs to serve /api/mobile/v1.
54
+ // 0.2.0 移除了 `settings.register(namespace, schema, { base, applies })` 那套 API,所以不再
55
+ // 注入 `settings`:配置改由插件自己的 relay.json 存储负责(见下面 createConfigStore)。
56
+ // 0.2.0 里也没有 `apiProxy` 这个服务,宿主 RPC 全走 `typertGateway`;`ctx.apiProxy` 的
57
+ // 旧形状由 apply 在本 fiber 上 provide 的兼容桥兜住(fiber.store 里就有,get trap 第一站
58
+ // 就命中)。漏了这条分支,读 `ctx.apiProxy` 会抛 "without inject",服务器宿主上 mirror
59
+ // 推送曾整条挂掉。
56
60
  //
57
- // 0.2.0 移除了 `settings.register(namespace, schema, { base, applies })` 这套 API
58
- // (`@deepseek-ai/dsh-settings` 现在只导出 SettingsForms / redactSecrets /
59
- // SettingsConflictError),所以这里不再注入 `settings`,配置改由插件自己的
60
- // relay.json 存储负责 —— 见下面 createConfigStore。
61
+ // 只有 `webServer` 是硬依赖:没有它这个插件一行路由都注册不上,等不到就是真的没得做。
61
62
  //
62
- // 0.2.0 里也没有 `apiProxy` 这个服务(整个 asar 零命中):宿主 RPC 全部走
63
- // `ctx.get('typertGateway')` 的 `invoke({ namespace, method, args })`,见
64
- // mobile/plugins.js、mobile/references.js 里同款封装。留着 apiProxy 只会让插件永远
65
- // pending —— 服务不存在时 loader 不会激活这一行,于是路由、sidecar、中转全都不出现,
66
- // 而日志只有一行 "pending (waiting for service: apiProxy)"。
63
+ // 其余服务**一律不写进 inject**,改成用到时 `ctx.get`。cordis 的 inject 是「缺一个就整行
64
+ // pending」:`typertGateway` 由后装载的另一行提供、`sessions` 来自别的 bundle,任何一个被
65
+ // 别人关掉 / 换掉 / 延迟,注进去的代价都是路由、sidecar、配对、二维码**一起不出现**,日志
66
+ // 里只剩一行 pending —— 手机上表现为「装了插件但完全连不上」。
67
67
  //
68
- // 所以 `apiProxy` 是**按运行时二选一**的:
69
- // 旧运行时(自带 @deepseek-ai/dsh-host-apiproxy)→ 注入它,读宿主那份真服务;
70
- // 0.2.0 → 不注入,改由 apply 在本 fiber 上
71
- // provide 兼容桥(fiber.store 里
72
- // 有,get trap 第一站就命中)。
73
- // 漏了这两个分支里的任何一个,读 `ctx.apiProxy` 都会抛
74
- // `cannot get property "apiProxy" without inject`:服务器宿主上 mirror 推送曾整条挂掉。
68
+ // 取不到时如实降级:宿主 RPC 那条回 `host-unavailable`(见 host-bridge/transport.js 的
69
+ // gatewayOf,它每次调用都现取),路由、局域网直连、控制面、镜像照常工作。实时流那条在
70
+ // 网关永远不出现时保持空闲 + 心跳(`host-stream-unavailable`),下一次重连就会接上。
75
71
  //
76
- // `typertGateway` 必须出现在 inject 里(0.2.0 那一支):宿主 RPC 全部经它派发,
77
- // 而它是**后于本插件**装载的服务。只 `ctx.get('typertGateway')` 而不注入,取到的是
78
- // 装载那一刻的空值,于是每一条事件流都抛「当前宿主版本没有提供该能力」——服务器宿主
79
- // 上实时推送整条挂掉,而收发消息、会话列表这些走普通请求的功能一切正常,看着像只有
80
- // 「刷新不实时」的小毛病。注入之后 cordis 会等服务就绪再激活本插件。
81
- // 旧运行时那一支不加:那版没有这个服务,注入只会让这一行永远 pending。
82
- export const inject = runtimeProvidesApiProxy()
83
- ? ['webServer', 'apiProxy', 'sessions']
84
- : ['webServer', 'sessions', 'typertGateway']
72
+ // 旧运行时那一支多一个 `apiProxy`:宿主自己提供它(`@deepseek-ai/dsh-host-apiproxy` 装上
73
+ // 了,就一定会被提供),读 `ctx.apiProxy` 必须有 inject,否则 get trap 直接抛。
74
+ export const inject = runtimeProvidesApiProxy() ? ['webServer', 'apiProxy'] : ['webServer']
85
75
 
86
76
  export const REMOTE_CONTROL_SETTINGS_NAMESPACE = 'mobile-remote-control'
87
77
  export const RemoteControlSettingsSchema = z.object({
@@ -258,7 +248,27 @@ function apply(ctx) {
258
248
  // 这两条二选一,判据同源:0.1.x 宿主自带真 apiProxy(审批帧与回答都还走得通),
259
249
  // 0.2.x 才需要兼容桥,也才需要审批桥(见 mobile/approvals.js 的文件头)。
260
250
  const hasHostApiProxy = runtimeProvidesApiProxy()
261
- if (!hasHostApiProxy) ctx.reflect.provide('apiProxy', createApiProxy(ctx))
251
+ /** 装配层自己的告警出口:拿不到 logger 也照样往下走,不打日志不是失败。 */
252
+ const warn = (message, ...rest) => {
253
+ try { ctx.logger?.warn?.(`${name}: ${message}`, ...rest) } catch { /* 日志本身不许反噬装配 */ }
254
+ }
255
+ /**
256
+ * 每一块装配各自兜住:一块挂不上只留一行日志,剩下的照常启动。
257
+ *
258
+ * 代价说清楚:别人撞了同一个路由、先占了一个服务名,缺的必须是**那一块**,不能是整张
259
+ * 移动 API —— 装配层里一个没兜住的 throw 会让 fiber 回滚,已经注册好的路由一起消失。
260
+ */
261
+ const mount = (label, fn) => {
262
+ try { return fn() } catch (error) {
263
+ warn(`${label} 没有挂上:%s`, error?.message || error)
264
+ return undefined
265
+ }
266
+ }
267
+ if (!hasHostApiProxy) {
268
+ // `apiProxy` 是公共名字:另一个兼容旧 API 的插件也可能 provide 它。撞上时用对方那份
269
+ // (同形状、同语义),不跟它抢 —— 抢的结果是我们自己整块装配失败,手机那边全断。
270
+ mount('apiProxy 兼容桥', () => ctx.reflect.provide('apiProxy', createApiProxy(ctx)))
271
+ }
262
272
 
263
273
  const state = core.loadState()
264
274
  // 0.2.0 之前是 `ctx.settings.register(REMOTE_CONTROL_SETTINGS_NAMESPACE,
@@ -329,11 +339,11 @@ function apply(ctx) {
329
339
  // 隧道刚连上时补推一次全量:未连通期间每轮推送都被 worker 以 503 拒绝。
330
340
  sidecarLifecycle.onTunnelUp(() => { void mirror.pushMirror(); })
331
341
 
332
- ctx.effect(() => sidecarLifecycle.start(), `${name}: facade sidecar`)
333
- ctx.effect(() => mirror.start(), `${name}: mobile mirror sync`)
342
+ mount('sidecar', () => ctx.effect(() => sidecarLifecycle.start(), `${name}: facade sidecar`))
343
+ mount('镜像推送', () => ctx.effect(() => mirror.start(), `${name}: mobile mirror sync`))
334
344
 
335
- if (!hasHostApiProxy) ctx.effect(() => installApprovalBridge(ctx), `${name}: host approval bridge`)
336
- if (!hasHostApiProxy) ctx.effect(() => installQuestionBridge(ctx), `${name}: host question bridge`)
345
+ if (!hasHostApiProxy) mount('审批桥', () => ctx.effect(() => installApprovalBridge(ctx), `${name}: host approval bridge`))
346
+ if (!hasHostApiProxy) mount('提问桥', () => ctx.effect(() => installQuestionBridge(ctx), `${name}: host question bridge`))
337
347
  ctx.effect(() => {
338
348
  // Profiles without the optional tools injection still support core remote control.
339
349
  try { installSessionCleanup(ctx) } catch (error) {
@@ -348,7 +358,7 @@ function apply(ctx) {
348
358
  }
349
359
  return undefined
350
360
  }, `${name}: extension tool`)
351
- ctx.effect(() => statsScheduler.start(ctx), `${name}: stats collect`)
361
+ mount('统计采集', () => ctx.effect(() => statsScheduler.start(ctx), `${name}: stats collect`))
352
362
 
353
363
  const status = createStatusView({
354
364
  state,
@@ -365,11 +375,24 @@ function apply(ctx) {
365
375
  function applyServices() {
366
376
  void syncSidecar().catch(() => undefined)
367
377
  }
378
+ /**
379
+ * 一条路由挂不上只丢这一条。
380
+ *
381
+ * `webServer.register` 对重复地址是抛错(另一个插件用了同一个路径、上一代没卸干净都在
382
+ * 这一类),而这个调用本来就在 effect 里 —— 让它抛出去会把整个 fiber 带走,连已经挂上
383
+ * 的几百条一起消失。所以这里逐条兜住,日志里留下路径,别让手机端拿到一片 404。
384
+ */
385
+ const mountRoute = (route, upgrade) => mount(`路由 ${route.path}`, () => ctx.effect(() => {
386
+ try { return upgrade ? ctx.webServer.registerUpgrade(route) : ctx.webServer.register(route) } catch (error) {
387
+ warn('路由 %s 没挂上:%s', route.path, error?.message || error)
388
+ return () => {}
389
+ }
390
+ }, `${name}: ${route.path}`))
368
391
  function registerRoute(route) {
369
- ctx.effect(() => ctx.webServer.register(route), `${name}: ${route.path}`)
392
+ mountRoute(route)
370
393
  }
371
394
 
372
- registerControlPlaneRoutes({
395
+ mount('控制面路由', () => registerControlPlaneRoutes({
373
396
  registerRoute,
374
397
  // 更新的机制住在外壳(`lib/index.js`)里,代这边只把它开成本机接口。取不到就如实报
375
398
  // 不可用(`lib/impl.js` 被单独挂载时就是这样)。
@@ -408,7 +431,7 @@ function apply(ctx) {
408
431
  // 吊销时收掉这台设备已经开着的流(§3.3):token 换了只保证「下一次请求」被拒,
409
432
  // 一条已经开着的 SSE 不会自己停,被踢的手机还能继续收消息直到它自己重连。
410
433
  dropDeviceStreams: (deviceId) => streamRegistry.drop(deviceId),
411
- })
434
+ }))
412
435
 
413
436
 
414
437
  /**
@@ -452,13 +475,11 @@ function apply(ctx) {
452
475
 
453
476
  // 移动端路由表在 lib/routes/mobile-api.js:那里是一张可以对着 App 侧 apiPaths.ts
454
477
  // 核对的「路径 → handler」表,不用在 1000 行装配代码里翻。
455
- registerMobileRoutes({
478
+ mount('移动端路由', () => registerMobileRoutes({
456
479
  registerRoute,
457
480
  // 预览是 WebSocket 升级路由,走的是 webServer 的另一套名字空间;与 registerRoute
458
481
  // 一样必须包在 ctx.effect 里,否则插件重载时重复注册会直接抛错。
459
- registerUpgradeRoute: (route) => {
460
- ctx.effect(() => ctx.webServer.registerUpgrade(route), `${name}: ${route.path}`)
461
- },
482
+ registerUpgradeRoute: (route) => mountRoute(route, true),
462
483
  preview,
463
484
  guarded,
464
485
  state,
@@ -495,8 +516,8 @@ function apply(ctx) {
495
516
  credentials,
496
517
  activeGuard,
497
518
  },
498
- })
499
- ctx.effect(() => {
519
+ }))
520
+ mount('服务同步', () => ctx.effect(() => {
500
521
  const stopWatching = settings.watch(next => {
501
522
  Object.assign(state, next)
502
523
  core.saveState(state)
@@ -509,7 +530,7 @@ function apply(ctx) {
509
530
  // 那个 effect 独占。两个 effect 都停一次会让 worker 在 dispose 期间收到
510
531
  // 两次 stop,第二次撞上已关闭的进程句柄并抛出未捕获异常。
511
532
  }
512
- }, `${name}: services`)
533
+ }, `${name}: services`))
513
534
  }
514
535
 
515
536
  export { apply }
package/lib/index.js CHANGED
@@ -31,14 +31,14 @@ import { createDeviceOta } from './shell/device-ota.js'
31
31
  import { installHostHalf } from './shell/half-route.js'
32
32
 
33
33
  export const name = 'dsh-mobile-plugin'
34
- export const inject = ['webServer', 'sessions']
34
+ // 外壳同样只等 `webServer`:它不读 `sessions`,多注一个只是多一种「整行 pending」的理由。
35
+ export const inject = ['webServer']
35
36
 
36
37
  /**
37
38
  * 装载当前代,并把「查 / 装 / 换代码」这套动作暴露成服务。
38
39
  *
39
- * 对照 `lib/impl.js` 的 `apply`:那边签名也是 `(ctx)`,只是它注册的是真正的移动 API;
40
- * 这里注册的是「装载它」。两者的 effect 各自归属自己那层 fiber —— 外壳被卸载时,代的
41
- * fiber 作为它的子节点一起被 dispose,代里那些 `ctx.effect` 的清理动作照常执行。
40
+ * 对照 `lib/impl.js` 的 `apply`:那边注册真正的移动 API,这里注册「装载它」。两份 effect
41
+ * 各自归属自己那层 fiber —— 外壳被卸载时,代的 fiber 作为子节点一起 dispose。
42
42
  */
43
43
  export function apply(ctx) {
44
44
  const runtime = createRuntime()
@@ -97,32 +97,31 @@ export function apply(ctx) {
97
97
  * 就重试」都在 `updater.performUpdate` 里 —— 入口只留装载逻辑,行数钉着。
98
98
  */
99
99
  async function applyUpdate({ force = false, auto = false } = {}) {
100
- return updater.performUpdate({
101
- runtime, base, hostService, updateState, force, auto, log,
102
- beforeInstall: snapshotRunning,
103
- })
100
+ return updater.performUpdate({ runtime, base, hostService, updateState, force, auto, log, beforeInstall: snapshotRunning })
104
101
  }
105
102
 
106
- // 换代前要知道有没有请求在飞(见 runtime.drain)。宿主 webServer 没有中间件机制,所以
107
- // 这里包一层 register,把每条路由的 handler 变成「进 +1 / 出 -1」。包的时机在任何一代
108
- // 装载之前,且 `wrapHandler` 遇到不认识的形状会原样放行。
103
+ // 换代前要知道有没有请求在飞(见 runtime.drain)。webServer 没有中间件机制,所以包一层
104
+ // register 把每条 handler 变成「进 +1 / 出 -1」(`wrapHandler` 认不出的形状原样放行)。
105
+ //
106
+ // 这层包装必须**可叠加**:别人后来也包一层时闭包着我们的包装,撤销时无条件还原会把
107
+ // 别人的那层一起摘掉。所以只在「现在挂的就是我们这层」时才还原。
109
108
  ctx.effect(() => {
110
109
  const server = ctx.webServer
111
- const original = server?.register
112
- if (typeof original !== 'function') return () => {}
113
- server.register = function register(route) {
114
- return original.call(this, runtime.wrapHandler(route))
110
+ const outer = server?.register
111
+ if (typeof outer !== 'function') return () => {}
112
+ const wrapper = function register(route) {
113
+ return outer.call(this, runtime.wrapHandler(route))
115
114
  }
115
+ server.register = wrapper
116
116
  return () => {
117
- server.register = original
117
+ if (server.register === wrapper) server.register = outer
118
118
  return runtime.detach()
119
119
  }
120
120
  }, 'dsh-mobile-plugin: generation runtime')
121
121
 
122
122
  const installedShellHash = () => updater.shellFingerprint(installedPackageDir())
123
-
124
- // 宿主半边的自检口(实现在 `shell/half-route.js`):实现装载失败时整张控制面路由
125
- // 都不在,只有外壳还活着 —— 那是「它为什么没起来」唯一说得通的落点。
123
+ // 宿主半边的自检口(`shell/half-route.js`):实现装载失败时整张控制面路由都不在,只有
124
+ // 外壳还活着 —— 那是「它为什么没起来」唯一说得通的落点。
126
125
  ctx.effect(() => installHostHalf(ctx, runtime, { installedVersion, installedShellHash, runningShellHash }), 'dsh-mobile-plugin: host half self check')
127
126
 
128
127
  ctx.reflect.provide('dshMobilePluginRuntime', {
@@ -163,9 +162,8 @@ export function apply(ctx) {
163
162
  .start(ctx, { log })
164
163
  .then(() => {
165
164
  log('info', 'dsh-mobile-plugin: 已装载 %s(来源 %s)', runtime.status().version, runtime.status().source)
166
- // 每 2 分钟让已落盘的正式版接管 OTA,并补上「盘上比运行中新」的那一次换代;
167
- // 自动安装仍由开关控制,开着就**不等正在跑的对话**(`auto` 那一档,见 update-install)。
168
- // 补换代不受开关管:它不下载任何东西,只是让已经在盘上的代码生效。
165
+ // 每 2 分钟让已落盘的正式版接管 OTA,并补上「盘上比运行中新」的那次换代(补换代不
166
+ // 受开关管:它不下载任何东西);自动安装由开关控制,开着就不等正在跑的对话。
169
167
  const timer = setInterval(() => {
170
168
  deviceOta.reconcileOfficial()
171
169
  .then(() => updater.reconcileInstalledGeneration({ runtime, log }))
@@ -179,6 +177,7 @@ export function apply(ctx) {
179
177
  ctx.effect(() => () => clearInterval(timer), 'dsh-mobile-plugin: auto update timer')
180
178
  // 启动后延后一次:别和绑端口、连隧道抢带宽。
181
179
  if (updater.autoUpdateEnabled()) {
180
+
182
181
  const kick = setTimeout(() => {
183
182
  applyUpdate({ auto: true }).catch(error => {
184
183
  updateState.lastError = String(error?.message || error)
@@ -45,9 +45,10 @@ const START_RETRY_MS = Number(process.env.DSH_MOBILE_START_RETRY_MS) > 0
45
45
  * 代没有声明 `inject` 时的兜底(只有测试里的假代会走这里)。
46
46
  *
47
47
  * 真实实现 `lib/impl.js` 一定声明 inject,而且是按运行时二选一的,所以这里**不能**
48
- * 拿它去覆盖代自己的声明 —— 那会把旧运行时要用的 `apiProxy` 抹掉。
48
+ * 拿它去覆盖代自己的声明 —— 那会把旧运行时要用的 `apiProxy` 抹掉。兜底值同样只留
49
+ * `webServer`:多一个服务就多一种「整行 pending」的理由。
49
50
  */
50
- const DEFAULT_INJECT = ['webServer', 'sessions']
51
+ const DEFAULT_INJECT = ['webServer']
51
52
 
52
53
  const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
53
54
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "dsh-mobile-plugin",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (dsh 0.2.x, peers resolved by the host)",
5
5
  "license": "MIT",
6
6
  "icon": "./icon.png",
7
7
  "engines": {
8
- "dsh": ">=0.2.0 <0.3.0"
8
+ "dsh": ">=0.2.0"
9
9
  },
10
10
  "type": "module",
11
11
  "main": "lib/index.js",