@linxin666/dsh-remote-web-ui 0.4.6 → 0.4.7
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/README.i18n.yaml +2 -2
- package/README.md +13 -13
- package/README.zh.md +13 -13
- package/lib/client.js +21 -90
- package/lib/client.js.map +1 -1
- package/lib/index.js +398 -43
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +3 -15
- package/lib/types/client/RemotePanel.d.ts +1 -9
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +7 -4
- package/lib/types/client/locales.d.ts +4 -8
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +4 -8
- package/lib/types/client/settings-entry-form.d.ts.map +1 -1
- package/lib/types/client/settings-entry-form.js +0 -1
- package/lib/types/index.d.ts +14 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +67 -17
- package/lib/types/lan-source.d.ts +65 -0
- package/lib/types/lan-source.d.ts.map +1 -0
- package/lib/types/lan-source.js +113 -0
- package/lib/types/lan.d.ts +49 -3
- package/lib/types/lan.d.ts.map +1 -1
- package/lib/types/lan.js +120 -6
- package/lib/types/remote-api.d.ts +7 -2
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +17 -2
- package/lib/types/routes.d.ts +71 -9
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +157 -27
- package/lib/types/tunnel-plan.d.ts +8 -2
- package/lib/types/tunnel-plan.d.ts.map +1 -1
- package/lib/types/tunnel-plan.js +9 -1
- package/package.json +20 -25
- package/src/client/RemoteEntry.tsx +2 -18
- package/src/client/RemotePanel.tsx +9 -43
- package/src/client/locales.ts +4 -8
- package/src/client/remote.module.css +2 -48
- package/src/client/settings-entry-form.ts +0 -1
- package/src/index.ts +77 -17
- package/src/lan-source.ts +132 -0
- package/src/lan.ts +126 -6
- package/src/remote-api.ts +24 -4
- package/src/routes.ts +178 -27
- package/src/tunnel-plan.ts +9 -2
- package/lib/invariant.js +0 -21
- package/lib/types/invariant.d.ts +0 -16
- package/lib/types/invariant.d.ts.map +0 -1
- package/lib/types/invariant.js +0 -23
- package/src/invariant.ts +0 -32
package/src/routes.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { isLoopbackClient, readCookie } from './gate.ts'
|
|
|
29
29
|
import { readJsonBody, writeJson } from './http.ts'
|
|
30
30
|
import { createPairGrantStore, type PairGrantStore } from './pair-grant.ts'
|
|
31
31
|
import { REMOTE_HOST_GRANT_GLOBAL } from './remote-channel-rules.ts'
|
|
32
|
+
import { appShellLanSwitchScript, type LanSourceHop } from './lan-source.ts'
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Browser-trust fence for the /api/pair routes, mirroring the connection
|
|
@@ -293,19 +294,27 @@ const COOKIE_MAX_AGE_SEC = 365 * 24 * 60 * 60
|
|
|
293
294
|
* The cookieless device credential: pass the device id from the /pair-app
|
|
294
295
|
* URL into sessionStorage (and localStorage for tab reloads) before any app
|
|
295
296
|
* script runs - same key the boot patch and the channel gate read. The
|
|
296
|
-
* replaceState
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
297
|
+
* replaceState drops the one-time grant from the address bar and leaves the
|
|
298
|
+
* SPA on a path this plugin answers again; which path depends on the origin.
|
|
299
|
+
*
|
|
300
|
+
* A secure context registers the reopen service worker in the same breath,
|
|
301
|
+
* and that worker owns navigations to '/', so the SPA keeps its canonical
|
|
302
|
+
* root. A plain-HTTP LAN origin is never a secure context, so the worker
|
|
303
|
+
* cannot exist there and '/' falls through to the harness index gate the
|
|
304
|
+
* cookieless flow can never satisfy (issue #1867). The address bar therefore
|
|
305
|
+
* keeps the plugin-owned landing path, which re-validates the device
|
|
306
|
+
* credential on every reload and serves the bilingual re-pair page once the
|
|
307
|
+
* pairing is gone. Both targets are bare paths: the one-time grant never
|
|
308
|
+
* survives the rewrite, and no credential is ever left in the URL.
|
|
301
309
|
*/
|
|
302
310
|
export const APP_DEVICE_STORAGE_KEY = 'dsh-remote-device'
|
|
303
311
|
|
|
304
312
|
export function appShellCaptureScript(deviceId: string): string {
|
|
305
313
|
const safeId = JSON.stringify(deviceId)
|
|
306
314
|
const grantGlobal = JSON.stringify(REMOTE_HOST_GRANT_GLOBAL)
|
|
315
|
+
const reopenPath = JSON.stringify(PAIR_PATHS.appPage)
|
|
307
316
|
const register = `try{if('serviceWorker' in navigator){navigator.serviceWorker.register(${JSON.stringify(PAIR_PATHS.appServiceWorker)},{scope:'/'}).catch(function(e){})}}catch(e){}`
|
|
308
|
-
return `<script>(function(){try{sessionStorage.setItem(${JSON.stringify(APP_DEVICE_STORAGE_KEY)},${safeId});}catch(e){}try{history.replaceState(null,'','/')}catch(e){}try{window[${grantGlobal}]=true}catch(e){}${register}})()</script>`
|
|
317
|
+
return `<script>(function(){try{sessionStorage.setItem(${JSON.stringify(APP_DEVICE_STORAGE_KEY)},${safeId});}catch(e){}try{history.replaceState(null,'',window.isSecureContext?'/':${reopenPath})}catch(e){}try{window[${grantGlobal}]=true}catch(e){}${register}})()</script>`
|
|
309
318
|
}
|
|
310
319
|
|
|
311
320
|
/**
|
|
@@ -315,9 +324,19 @@ export function appShellCaptureScript(deviceId: string): string {
|
|
|
315
324
|
* hook only when this server-served marker is already set (see
|
|
316
325
|
* remote-channel-boot.ts), so host mode is granted by the device-gated landing
|
|
317
326
|
* instead of being asserted from the origin.
|
|
327
|
+
*
|
|
328
|
+
* A landing reached on a base other than the machine's own LAN origin also
|
|
329
|
+
* carries the source-decision script (lan-source.ts): the phone decides at
|
|
330
|
+
* connect time whether a LAN candidate answers, and switches its origin when
|
|
331
|
+
* one does. It runs after the capture script so the granted page is fully
|
|
332
|
+
* usable before the probe's outcome can move it.
|
|
333
|
+
* @param html - the official index document.
|
|
334
|
+
* @param deviceId - the device session this landing is being served for.
|
|
335
|
+
* @param hop - the LAN candidates to probe, when a switch is possible.
|
|
336
|
+
* @returns the patched document.
|
|
318
337
|
*/
|
|
319
|
-
export function patchAppShell(html: string, deviceId: string): string {
|
|
320
|
-
const script = appShellCaptureScript(deviceId)
|
|
338
|
+
export function patchAppShell(html: string, deviceId: string, hop?: LanSourceHop): string {
|
|
339
|
+
const script = appShellCaptureScript(deviceId) + (hop === undefined ? '' : appShellLanSwitchScript(hop))
|
|
321
340
|
const head = /<head[^>]*>/i.exec(html)
|
|
322
341
|
if (head === null) return script + html
|
|
323
342
|
const end = head.index + head[0].length
|
|
@@ -332,9 +351,10 @@ export function patchAppShell(html: string, deviceId: string): string {
|
|
|
332
351
|
* worker owns navigations to `/` instead: network-first through /pair-app
|
|
333
352
|
* (which validates the device cookie and refreshes its presence), the
|
|
334
353
|
* cached shell for offline opens, and a pass-through of the original
|
|
335
|
-
* request when the plugin no longer answers.
|
|
336
|
-
* secure
|
|
337
|
-
*
|
|
354
|
+
* request when the plugin no longer answers. A plain-HTTP LAN origin is
|
|
355
|
+
* never a secure context, so the worker cannot exist there; the capture
|
|
356
|
+
* script keeps the plugin-owned landing path in the address bar instead,
|
|
357
|
+
* and that landing re-validates the device credential on every reload.
|
|
338
358
|
*
|
|
339
359
|
* Kept as a plain string (same pattern as the capture script): it must load
|
|
340
360
|
* with no build step, run on every JS engine that ships service workers,
|
|
@@ -435,6 +455,12 @@ export const PAIR_PATHS = {
|
|
|
435
455
|
acceptPage: '/pair-accept',
|
|
436
456
|
/** The cookieless app landing: serves the official shell for a paired device. */
|
|
437
457
|
appPage: '/pair-app',
|
|
458
|
+
/**
|
|
459
|
+
* The landing page's LAN reachability beacon (lan-source.ts): an inert
|
|
460
|
+
* no-cors GET target, so a probe proves the origin answers without touching
|
|
461
|
+
* a credential or any state.
|
|
462
|
+
*/
|
|
463
|
+
ping: '/pair-ping',
|
|
438
464
|
/**
|
|
439
465
|
* The reopen service worker (registered by the capture script). Root-level
|
|
440
466
|
* path so its default script-directory scope already covers `/`.
|
|
@@ -527,12 +553,65 @@ export class PairingEventsStream {
|
|
|
527
553
|
}
|
|
528
554
|
}
|
|
529
555
|
|
|
556
|
+
/**
|
|
557
|
+
* The manual override of the automatic network choice, for the case where the
|
|
558
|
+
* phone's own probe misjudges (a firewall that answers the reachability ping
|
|
559
|
+
* but not the page, or a LAN the probe cannot see).
|
|
560
|
+
*
|
|
561
|
+
* - `auto` (default): the QR link names the public base when one is configured
|
|
562
|
+
* and the landing page probes the LAN candidates, switching to the first one
|
|
563
|
+
* that answers.
|
|
564
|
+
* - `public`: the public base only, and the landing page never probes. For a
|
|
565
|
+
* deployment where the LAN hop is undesirable.
|
|
566
|
+
* - `lan`: the first LAN literal directly, with no public hop. For a phone that
|
|
567
|
+
* is known to sit on this machine's own network.
|
|
568
|
+
*/
|
|
569
|
+
export type PairingSourcePreference = 'auto' | 'public' | 'lan'
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* The accepted {@link PairingSourcePreference} values, shared with the config
|
|
573
|
+
* schema so the patch that admits a value and the handler that reads it cannot
|
|
574
|
+
* drift apart.
|
|
575
|
+
*/
|
|
576
|
+
export const PAIRING_SOURCE_OPTIONS: readonly PairingSourcePreference[] = ['auto', 'public', 'lan']
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* The LAN origins a landing page served from one authority may hop to: every
|
|
580
|
+
* LAN base the service advertises except the authority serving the request
|
|
581
|
+
* (hopping to the current origin would be a no-op), normalized through `URL`
|
|
582
|
+
* so a default port compares equal to `location.origin`.
|
|
583
|
+
* @param lanBases - the advertised LAN base URLs, in interface order.
|
|
584
|
+
* @param currentOrigin - the origin serving the landing page.
|
|
585
|
+
* @returns the distinct candidate origins, in interface order.
|
|
586
|
+
*/
|
|
587
|
+
export function lanSourceCandidates(lanBases: readonly string[], currentOrigin: string): string[] {
|
|
588
|
+
const origins: string[] = []
|
|
589
|
+
for (const base of lanBases) {
|
|
590
|
+
let origin: string
|
|
591
|
+
try {
|
|
592
|
+
origin = new URL(base).origin
|
|
593
|
+
} catch {
|
|
594
|
+
continue
|
|
595
|
+
}
|
|
596
|
+
if (origin === currentOrigin || origins.includes(origin)) continue
|
|
597
|
+
origins.push(origin)
|
|
598
|
+
}
|
|
599
|
+
return origins
|
|
600
|
+
}
|
|
601
|
+
|
|
530
602
|
/** Route-family dependencies (test seam). */
|
|
531
603
|
export interface PairRoutesDeps {
|
|
532
604
|
/** The pairing service. */
|
|
533
605
|
service: PairingService
|
|
534
606
|
/** Current desktop gate policy, re-read for every status response. */
|
|
535
607
|
requirePairingForLan?: boolean | (() => boolean)
|
|
608
|
+
/**
|
|
609
|
+
* The manual override of the automatic network choice
|
|
610
|
+
* ({@link PairingSourcePreference}), re-read per request. Defaults to
|
|
611
|
+
* `auto`: the QR link names the public base and the landing page decides
|
|
612
|
+
* whether to switch to a LAN origin.
|
|
613
|
+
*/
|
|
614
|
+
pairingSource?: () => PairingSourcePreference
|
|
536
615
|
/**
|
|
537
616
|
* LAN-bind facts for the settings card (managed patch block state, live
|
|
538
617
|
* bind host/port, firewall summary). Re-read per request so a hot-reloaded
|
|
@@ -568,6 +647,28 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
|
|
|
568
647
|
const pairingRequired = (): boolean => typeof requirePairingForLan === 'function'
|
|
569
648
|
? requirePairingForLan()
|
|
570
649
|
: requirePairingForLan
|
|
650
|
+
/** The manual network override, re-read per request (default: automatic). */
|
|
651
|
+
const sourcePreference = (): PairingSourcePreference => deps.pairingSource?.() ?? 'auto'
|
|
652
|
+
/**
|
|
653
|
+
* Every authority this surface serves, as {@link isTrustedHost} wants them
|
|
654
|
+
* (bare `host` or `host:port` literals). The LAN literals are re-read per
|
|
655
|
+
* request: a hot rebind (the lan-bind toggle) updates them mid-process, and
|
|
656
|
+
* both the fence and the reachability beacon must follow.
|
|
657
|
+
*/
|
|
658
|
+
const servedAuthorities = (): string[] => {
|
|
659
|
+
const publicHost = publicHostOf(service.publicBaseUrl)
|
|
660
|
+
const extraHosts = typeof deps.trustedHosts === 'function' ? deps.trustedHosts() : (deps.trustedHosts ?? [])
|
|
661
|
+
return [
|
|
662
|
+
...service.lanAddresses,
|
|
663
|
+
...(publicHost !== undefined ? [publicHost] : []),
|
|
664
|
+
...extraHosts,
|
|
665
|
+
...dynamicTrustedHosts,
|
|
666
|
+
]
|
|
667
|
+
}
|
|
668
|
+
/** Every LAN base URL the service currently advertises, in interface order. */
|
|
669
|
+
const lanBaseUrls = (): string[] => service.lanAddresses
|
|
670
|
+
.map(address => service.lanBaseUrlFor(address))
|
|
671
|
+
.filter((base): base is string => base !== undefined)
|
|
571
672
|
const grants = deps.grants ?? createPairGrantStore()
|
|
572
673
|
const events = new PairingEventsStream(service)
|
|
573
674
|
const dynamicTrustedHosts = new Set<string>()
|
|
@@ -581,17 +682,7 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
|
|
|
581
682
|
* pairing entry pages), never for an API or subresource request.
|
|
582
683
|
*/
|
|
583
684
|
const lanFence = (req: IncomingMessage, navigation = false): boolean => {
|
|
584
|
-
const
|
|
585
|
-
// The service's LAN bases re-read per request: a hot rebind (the lan-bind
|
|
586
|
-
// toggle) updates them mid-process, and the fence must follow.
|
|
587
|
-
const bases = service.lanAddresses
|
|
588
|
-
const extraHosts = typeof deps.trustedHosts === 'function' ? deps.trustedHosts() : (deps.trustedHosts ?? [])
|
|
589
|
-
const combined = [
|
|
590
|
-
...bases,
|
|
591
|
-
...(publicHost !== undefined ? [publicHost] : []),
|
|
592
|
-
...extraHosts,
|
|
593
|
-
...dynamicTrustedHosts,
|
|
594
|
-
]
|
|
685
|
+
const combined = servedAuthorities()
|
|
595
686
|
if (navigation && isTopLevelDocumentNavigation(req) ? isTrustedHost(req, combined) : isTrustedApiRequest(req, combined)) return true
|
|
596
687
|
|
|
597
688
|
// If request carries a valid paired device cookie from a private LAN host (e.g. after service restart), trust and remember it
|
|
@@ -702,10 +793,23 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
|
|
|
702
793
|
// the injected portrait adaptation, PCs the full desktop UI), so the
|
|
703
794
|
// remote surface can never drift from the official one.
|
|
704
795
|
const { token, expiresAt } = service.issue(undefined, address)
|
|
705
|
-
// The
|
|
706
|
-
// phone anywhere can reach
|
|
707
|
-
//
|
|
708
|
-
|
|
796
|
+
// The advertised base is the public (tunneled/relayed) one whenever it is
|
|
797
|
+
// configured: it is the only base a phone anywhere can reach, so the QR
|
|
798
|
+
// never depends on the scanning phone being on this machine's network.
|
|
799
|
+
// The landing page then decides at connect time whether to move to a LAN
|
|
800
|
+
// origin (lan-source.ts). `lan` is the manual override that pins the QR to
|
|
801
|
+
// a LAN literal; an explicit address always names one.
|
|
802
|
+
const preference = sourcePreference()
|
|
803
|
+
const base = address !== undefined
|
|
804
|
+
? service.lanBaseUrlFor(address)
|
|
805
|
+
: preference === 'lan'
|
|
806
|
+
? service.lanBaseUrl
|
|
807
|
+
: preference === 'public'
|
|
808
|
+
// Pinned to public: with none configured there is no satisfiable
|
|
809
|
+
// link, which the caller reports as lan-required rather than
|
|
810
|
+
// silently advertising a base the override excluded.
|
|
811
|
+
? service.publicBaseUrl
|
|
812
|
+
: (service.publicBaseUrl ?? service.lanBaseUrl)
|
|
709
813
|
if (base === undefined) throw new Error('remote-web-ui: base unavailable')
|
|
710
814
|
writeJson(res, 200, {
|
|
711
815
|
ok: true,
|
|
@@ -992,7 +1096,50 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
|
|
|
992
1096
|
'referrer-policy': 'no-referrer',
|
|
993
1097
|
'set-cookie': [deviceCookie(req, service.config.cookieName, id)],
|
|
994
1098
|
})
|
|
995
|
-
res.end(patchAppShell(html, id))
|
|
1099
|
+
res.end(patchAppShell(html, id, lanHop(req, id)))
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* The LAN hop this landing may take, or undefined when there is nothing to
|
|
1104
|
+
* decide. Three conditions have to hold together: the automatic preference
|
|
1105
|
+
* (both overrides answer the question already — `lan` serves the QR from a
|
|
1106
|
+
* LAN origin, `public` forbids the move), at least one LAN candidate other
|
|
1107
|
+
* than this origin, and a fresh one-time grant for the target landing. The
|
|
1108
|
+
* grant is what lets the target origin serve the shell: the device cookie is
|
|
1109
|
+
* origin-scoped, so the LAN origin has no credential of its own, and the
|
|
1110
|
+
* grant table lives in this process regardless of which interface answered.
|
|
1111
|
+
* The candidate set is carried to the client as origins (lan-source.ts
|
|
1112
|
+
* compares them against `location.origin`).
|
|
1113
|
+
* @param req - the landing request (its authority is the current origin).
|
|
1114
|
+
* @param deviceId - the device session the landing is served for.
|
|
1115
|
+
* @returns the hop payload, or undefined when no switch is possible.
|
|
1116
|
+
*/
|
|
1117
|
+
const lanHop = (req: IncomingMessage, deviceId: string): LanSourceHop | undefined => {
|
|
1118
|
+
if (sourcePreference() !== 'auto') return undefined
|
|
1119
|
+
const candidates = lanSourceCandidates(lanBaseUrls(), appOrigin(req))
|
|
1120
|
+
if (candidates.length === 0) return undefined
|
|
1121
|
+
return { candidates, path: PAIR_PATHS.appPage, pingPath: PAIR_PATHS.ping, grant: grants.issue(deviceId).grant }
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* The landing page's LAN reachability beacon: a 204 for a Host this plugin
|
|
1126
|
+
* serves, a 403 otherwise. It carries no credential and no state — a phone
|
|
1127
|
+
* that reaches it has proved the origin answers, which is all the source
|
|
1128
|
+
* decision needs, and the fence's browser-marker half is deliberately not
|
|
1129
|
+
* applied here: the probe is cross-site by construction (a page on the
|
|
1130
|
+
* public origin fetching a LAN origin), so the marker check would turn every
|
|
1131
|
+
* probe into the same 403 that means "not ours" and make the two
|
|
1132
|
+
* indistinguishable.
|
|
1133
|
+
*/
|
|
1134
|
+
const handlePing = (req: IncomingMessage, res: ServerResponse): void => {
|
|
1135
|
+
if (!requireMethod(req, res, 'GET')) return
|
|
1136
|
+
if (!isTrustedHost(req, servedAuthorities())) {
|
|
1137
|
+
res.writeHead(403, { 'content-type': 'text/plain; charset=utf-8' })
|
|
1138
|
+
res.end()
|
|
1139
|
+
return
|
|
1140
|
+
}
|
|
1141
|
+
res.writeHead(204, { 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' })
|
|
1142
|
+
res.end()
|
|
996
1143
|
}
|
|
997
1144
|
|
|
998
1145
|
/**
|
|
@@ -1032,6 +1179,10 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
|
|
|
1032
1179
|
routes.push({ kind: 'exact', path: PAIR_PATHS.lanBind, handler: handleLanBind })
|
|
1033
1180
|
}
|
|
1034
1181
|
routes.push({ kind: 'exact', path: PAIR_PATHS.acceptPage, handler: handleAcceptPage })
|
|
1182
|
+
// The beacon is independent of the app landing: a deployment with no
|
|
1183
|
+
// indexDocument still advertises LAN bases, and a landing served some other
|
|
1184
|
+
// way may still probe them.
|
|
1185
|
+
routes.push({ kind: 'exact', path: PAIR_PATHS.ping, handler: handlePing })
|
|
1035
1186
|
if (deps.indexDocument !== undefined) {
|
|
1036
1187
|
routes.push({ kind: 'exact', path: PAIR_PATHS.appPage, handler: handleAppPage })
|
|
1037
1188
|
// The reopen worker is only meaningful when the app landing it
|
package/src/tunnel-plan.ts
CHANGED
|
@@ -28,14 +28,21 @@ export type TunnelPlan =
|
|
|
28
28
|
* token AND a valid public hostname (the token does not carry the hostname,
|
|
29
29
|
* so the QR base and the fence trust would have no source without it);
|
|
30
30
|
* otherwise no tunnel and the manual public base applies.
|
|
31
|
+
*
|
|
32
|
+
* The quick target URL names the local webServer port, so a plan made before
|
|
33
|
+
* the listener binds cannot be quick: it comes back `off` and the caller —
|
|
34
|
+
* which can observe the port settle — re-plans. The planner itself never
|
|
35
|
+
* emits an `http://127.0.0.1:undefined` target.
|
|
31
36
|
* @param value - the resolved settings slice that drives the tunnel.
|
|
32
|
-
* @param port - the local webServer port the tunnel forwards to
|
|
37
|
+
* @param port - the local webServer port the tunnel forwards to (undefined
|
|
38
|
+
* before the listener binds).
|
|
33
39
|
*/
|
|
34
40
|
export function tunnelPlanOf(
|
|
35
41
|
value: { autoTunnel?: boolean; tunnelToken?: string; publicBaseUrl?: string },
|
|
36
|
-
port: number,
|
|
42
|
+
port: number | undefined,
|
|
37
43
|
): TunnelPlan {
|
|
38
44
|
if (value.autoTunnel === true) {
|
|
45
|
+
if (port === undefined) return { mode: 'off' }
|
|
39
46
|
const ignored: Array<'tunnelToken' | 'publicBaseUrl'> = []
|
|
40
47
|
if (value.tunnelToken !== undefined && value.tunnelToken !== '') ignored.push('tunnelToken')
|
|
41
48
|
if (value.publicBaseUrl !== undefined && value.publicBaseUrl !== '') ignored.push('publicBaseUrl')
|
package/lib/invariant.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
//#region src/invariant.ts
|
|
2
|
-
const PACKAGE_NAME = "@linxin666/dsh-remote-web-ui";
|
|
3
|
-
/** Cordis companion plugin name. */
|
|
4
|
-
const name = "remote-web-ui-invariant";
|
|
5
|
-
/** Service required before the companion can reserve package ownership. */
|
|
6
|
-
const inject = ["invariants"];
|
|
7
|
-
/**
|
|
8
|
-
* No runtime invariant: this package owns no durable package-local event
|
|
9
|
-
* stream; its route-table and device-session relationships are asserted by
|
|
10
|
-
* the package's own specs (route register/dispose symmetry via the
|
|
11
|
-
* webServer disposer contract, token/revocation semantics on the service).
|
|
12
|
-
*/
|
|
13
|
-
const install = () => {};
|
|
14
|
-
/**
|
|
15
|
-
* Register this package's invariant companion.
|
|
16
|
-
* @param ctx - Cordis context carrying the invariant service.
|
|
17
|
-
* @returns the installed registration's disposer after setup succeeds.
|
|
18
|
-
*/
|
|
19
|
-
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
20
|
-
//#endregion
|
|
21
|
-
export { apply, inject, name };
|
package/lib/types/invariant.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Package-owned invariant companion for `@linxin666/dsh-remote-web-ui`.
|
|
3
|
-
* @module @linxin666/dsh-remote-web-ui/invariant
|
|
4
|
-
*/
|
|
5
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
-
/** Cordis companion plugin name. */
|
|
7
|
-
export declare const name = "remote-web-ui-invariant";
|
|
8
|
-
/** Service required before the companion can reserve package ownership. */
|
|
9
|
-
export declare const inject: string[];
|
|
10
|
-
/**
|
|
11
|
-
* Register this package's invariant companion.
|
|
12
|
-
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
-
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
-
*/
|
|
15
|
-
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
-
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invariant.d.ts","sourceRoot":"","sources":["../../src/invariant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,oCAAoC;AACpC,eAAO,MAAM,IAAI,4BAA4B,CAAA;AAC7C,2EAA2E;AAC3E,eAAO,MAAM,MAAM,UAAiB,CAAA;AAUpC;;;;GAIG;AACH,eAAO,MAAM,KAAK,GAAI,KAAK,OAAO,KAAG,OAAO,CAAC,MAAM,IAAI,CACU,CAAA"}
|
package/lib/types/invariant.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Package-owned invariant companion for `@linxin666/dsh-remote-web-ui`.
|
|
3
|
-
* @module @linxin666/dsh-remote-web-ui/invariant
|
|
4
|
-
*/
|
|
5
|
-
const PACKAGE_NAME = '@linxin666/dsh-remote-web-ui';
|
|
6
|
-
/** Cordis companion plugin name. */
|
|
7
|
-
export const name = 'remote-web-ui-invariant';
|
|
8
|
-
/** Service required before the companion can reserve package ownership. */
|
|
9
|
-
export const inject = ['invariants'];
|
|
10
|
-
/**
|
|
11
|
-
* No runtime invariant: this package owns no durable package-local event
|
|
12
|
-
* stream; its route-table and device-session relationships are asserted by
|
|
13
|
-
* the package's own specs (route register/dispose symmetry via the
|
|
14
|
-
* webServer disposer contract, token/revocation semantics on the service).
|
|
15
|
-
*/
|
|
16
|
-
const install = () => { };
|
|
17
|
-
/**
|
|
18
|
-
* Register this package's invariant companion.
|
|
19
|
-
* @param ctx - Cordis context carrying the invariant service.
|
|
20
|
-
* @returns the installed registration's disposer after setup succeeds.
|
|
21
|
-
*/
|
|
22
|
-
export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
23
|
-
/* jscpd:ignore-end */
|
package/src/invariant.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Package-owned invariant companion for `@linxin666/dsh-remote-web-ui`.
|
|
3
|
-
* @module @linxin666/dsh-remote-web-ui/invariant
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/* jscpd:ignore-start */
|
|
7
|
-
import type { Context } from '@deepseek-ai/cordis'
|
|
8
|
-
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
|
9
|
-
|
|
10
|
-
const PACKAGE_NAME = '@linxin666/dsh-remote-web-ui'
|
|
11
|
-
|
|
12
|
-
/** Cordis companion plugin name. */
|
|
13
|
-
export const name = 'remote-web-ui-invariant'
|
|
14
|
-
/** Service required before the companion can reserve package ownership. */
|
|
15
|
-
export const inject = ['invariants']
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* No runtime invariant: this package owns no durable package-local event
|
|
19
|
-
* stream; its route-table and device-session relationships are asserted by
|
|
20
|
-
* the package's own specs (route register/dispose symmetry via the
|
|
21
|
-
* webServer disposer contract, token/revocation semantics on the service).
|
|
22
|
-
*/
|
|
23
|
-
const install: InvariantInstaller = () => {}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Register this package's invariant companion.
|
|
27
|
-
* @param ctx - Cordis context carrying the invariant service.
|
|
28
|
-
* @returns the installed registration's disposer after setup succeeds.
|
|
29
|
-
*/
|
|
30
|
-
export const apply = (ctx: Context): Promise<() => void> =>
|
|
31
|
-
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|
|
32
|
-
/* jscpd:ignore-end */
|