@linxin666/dsh-remote-web-ui 0.3.6 → 0.3.9
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 +77 -468
- package/README.zh.md +83 -113
- package/lib/client.js +955 -203
- package/lib/client.js.map +1 -1
- package/lib/index.js +1136 -1466
- package/lib/types/client/FooterRemoteEntry.d.ts +6 -7
- package/lib/types/client/FooterRemoteEntry.d.ts.map +1 -1
- package/lib/types/client/FooterRemoteEntry.js +1 -1
- package/lib/types/client/RemoteEntry.d.ts +7 -4
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +7 -10
- package/lib/types/client/RemotePanel.d.ts +2 -2
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +2 -2
- package/lib/types/client/RemoteSettingsCard.d.ts +9 -5
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +56 -3
- package/lib/types/client/deep-link.d.ts +10 -13
- package/lib/types/client/deep-link.d.ts.map +1 -1
- package/lib/types/client/deep-link.js +14 -74
- package/lib/types/client/index.d.ts +2 -6
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +73 -39
- package/lib/types/client/locales.d.ts +26 -12
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +36 -22
- package/lib/types/client/mobile-adapt.d.ts +40 -0
- package/lib/types/client/mobile-adapt.d.ts.map +1 -0
- package/lib/types/client/mobile-adapt.js +967 -0
- package/lib/types/client/pair-api.d.ts +24 -6
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +9 -14
- package/lib/types/client/remote-channel.d.ts +3 -0
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +35 -3
- package/lib/types/client/settings-form.d.ts +35 -43
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +60 -53
- package/lib/types/firewall.d.ts +75 -0
- package/lib/types/firewall.d.ts.map +1 -0
- package/lib/types/firewall.js +184 -0
- package/lib/types/gate.d.ts +11 -5
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +11 -5
- package/lib/types/index.d.ts +39 -10
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +206 -58
- package/lib/types/inner-auth.d.ts +32 -0
- package/lib/types/inner-auth.d.ts.map +1 -0
- package/lib/types/inner-auth.js +72 -0
- package/lib/types/lan-bind-plan.d.ts +55 -0
- package/lib/types/lan-bind-plan.d.ts.map +1 -0
- package/lib/types/lan-bind-plan.js +60 -0
- package/lib/types/lan-bind.d.ts +69 -0
- package/lib/types/lan-bind.d.ts.map +1 -0
- package/lib/types/lan-bind.js +128 -0
- package/lib/types/loopback-proxy.d.ts +9 -2
- package/lib/types/loopback-proxy.d.ts.map +1 -1
- package/lib/types/loopback-proxy.js +24 -2
- package/lib/types/pairing.d.ts +15 -3
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +20 -6
- package/lib/types/remote-api.d.ts +30 -13
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +55 -42
- package/lib/types/remote-channel-boot.d.ts +21 -2
- package/lib/types/remote-channel-boot.d.ts.map +1 -1
- package/lib/types/remote-channel-boot.js +65 -3
- package/lib/types/remote-channel-rules.d.ts +8 -0
- package/lib/types/remote-channel-rules.d.ts.map +1 -1
- package/lib/types/remote-channel-rules.js +9 -2
- package/lib/types/remote-methods.d.ts +45 -11
- package/lib/types/remote-methods.d.ts.map +1 -1
- package/lib/types/remote-methods.js +56 -27
- package/lib/types/remote-presence-pet.d.ts +51 -0
- package/lib/types/remote-presence-pet.d.ts.map +1 -0
- package/lib/types/remote-presence-pet.js +101 -0
- package/lib/types/routes.d.ts +76 -4
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +336 -19
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +7 -1
- package/package.json +20 -18
- package/src/client/FooterRemoteEntry.tsx +9 -10
- package/src/client/RemoteEntry.tsx +13 -14
- package/src/client/RemotePanel.tsx +5 -16
- package/src/client/RemoteSettingsCard.tsx +72 -13
- package/src/client/deep-link.ts +14 -75
- package/src/client/index.ts +77 -39
- package/src/client/locales.ts +36 -22
- package/src/client/mobile-adapt.ts +913 -0
- package/src/client/pair-api.ts +26 -15
- package/src/client/remote-channel.ts +31 -3
- package/src/client/settings-form.ts +78 -92
- package/src/dsh-home.ts +1 -1
- package/src/firewall.ts +230 -0
- package/src/gate.ts +11 -5
- package/src/index.ts +235 -65
- package/src/inner-auth.ts +85 -0
- package/src/lan-bind-plan.ts +81 -0
- package/src/lan-bind.ts +145 -0
- package/src/loopback-proxy.ts +34 -0
- package/src/pairing.persist.test.ts +1 -1
- package/src/pairing.ts +20 -6
- package/src/remote-api.ts +67 -45
- package/src/remote-channel-boot.ts +70 -3
- package/src/remote-channel-rules.ts +17 -2
- package/src/remote-methods.ts +58 -27
- package/src/remote-presence-pet.ts +129 -0
- package/src/routes.ts +358 -19
- package/src/tunnel.ts +7 -1
- package/assets/apple-touch-icon.png +0 -0
- package/assets/mobile-icon-192.png +0 -0
- package/assets/mobile-icon-512.png +0 -0
- package/assets/mobile-service-worker.js +0 -63
- package/lib/mobile.js +0 -17658
- package/lib/mobile.js.map +0 -1
- package/lib/types/mobile/PairRequiredView.d.ts +0 -7
- package/lib/types/mobile/PairRequiredView.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.js +0 -28
- package/lib/types/mobile/PairRequiredView.test.d.ts +0 -2
- package/lib/types/mobile/PairRequiredView.test.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.test.js +0 -24
- package/lib/types/mobile/api.d.ts +0 -145
- package/lib/types/mobile/api.d.ts.map +0 -1
- package/lib/types/mobile/api.js +0 -103
- package/lib/types/mobile/index.d.ts +0 -10
- package/lib/types/mobile/index.d.ts.map +0 -1
- package/lib/types/mobile/index.js +0 -38
- package/lib/types/mobile/markdown.d.ts +0 -34
- package/lib/types/mobile/markdown.d.ts.map +0 -1
- package/lib/types/mobile/markdown.js +0 -285
- package/lib/types/mobile/markdown.test.d.ts +0 -2
- package/lib/types/mobile/markdown.test.d.ts.map +0 -1
- package/lib/types/mobile/markdown.test.js +0 -79
- package/lib/types/mobile/messages.d.ts +0 -149
- package/lib/types/mobile/messages.d.ts.map +0 -1
- package/lib/types/mobile/messages.js +0 -561
- package/lib/types/mobile/messages.test.d.ts +0 -2
- package/lib/types/mobile/messages.test.d.ts.map +0 -1
- package/lib/types/mobile/messages.test.js +0 -356
- package/lib/types/mobile/mobile-styles.d.ts +0 -6
- package/lib/types/mobile/mobile-styles.d.ts.map +0 -1
- package/lib/types/mobile/mobile-styles.js +0 -1438
- package/lib/types/mobile/mobile-theme.d.ts +0 -22
- package/lib/types/mobile/mobile-theme.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.js +0 -70
- package/lib/types/mobile/mobile-theme.test.d.ts +0 -2
- package/lib/types/mobile/mobile-theme.test.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.test.js +0 -71
- package/lib/types/mobile/mux.d.ts +0 -128
- package/lib/types/mobile/mux.d.ts.map +0 -1
- package/lib/types/mobile/mux.js +0 -305
- package/lib/types/mobile/mux.test.d.ts +0 -2
- package/lib/types/mobile/mux.test.d.ts.map +0 -1
- package/lib/types/mobile/mux.test.js +0 -267
- package/lib/types/mobile/pairing.d.ts +0 -31
- package/lib/types/mobile/pairing.d.ts.map +0 -1
- package/lib/types/mobile/pairing.js +0 -62
- package/lib/types/mobile/rpc.d.ts +0 -38
- package/lib/types/mobile/rpc.d.ts.map +0 -1
- package/lib/types/mobile/rpc.js +0 -83
- package/lib/types/mobile/theme-toggle.d.ts +0 -12
- package/lib/types/mobile/theme-toggle.d.ts.map +0 -1
- package/lib/types/mobile/theme-toggle.js +0 -26
- package/lib/types/mobile/views/App.d.ts +0 -57
- package/lib/types/mobile/views/App.d.ts.map +0 -1
- package/lib/types/mobile/views/App.js +0 -150
- package/lib/types/mobile/views/App.test.d.ts +0 -2
- package/lib/types/mobile/views/App.test.d.ts.map +0 -1
- package/lib/types/mobile/views/App.test.js +0 -52
- package/lib/types/mobile/views/ChatView.d.ts +0 -63
- package/lib/types/mobile/views/ChatView.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.js +0 -821
- package/lib/types/mobile/views/ChatView.stream.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.stream.test.js +0 -236
- package/lib/types/mobile/views/ChatView.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.test.js +0 -718
- package/lib/types/mobile/views/SessionListView.d.ts +0 -27
- package/lib/types/mobile/views/SessionListView.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.js +0 -139
- package/lib/types/mobile/views/SessionListView.test.d.ts +0 -2
- package/lib/types/mobile/views/SessionListView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.test.js +0 -167
- package/lib/types/mobile/views/WorkspaceView.d.ts +0 -21
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.js +0 -93
- package/lib/types/mobile/views/WorkspaceView.test.d.ts +0 -2
- package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.test.js +0 -183
- package/lib/types/mobile-api.d.ts +0 -65
- package/lib/types/mobile-api.d.ts.map +0 -1
- package/lib/types/mobile-api.js +0 -404
- package/lib/types/mobile-pending.d.ts +0 -38
- package/lib/types/mobile-pending.d.ts.map +0 -1
- package/lib/types/mobile-pending.js +0 -59
- package/lib/types/mobile-pwa.d.ts +0 -6
- package/lib/types/mobile-pwa.d.ts.map +0 -1
- package/lib/types/mobile-pwa.js +0 -17
- package/lib/types/mobile-routes.d.ts +0 -13
- package/lib/types/mobile-routes.d.ts.map +0 -1
- package/lib/types/mobile-routes.js +0 -217
- package/lib/types/paired-model-catalog.d.ts +0 -17
- package/lib/types/paired-model-catalog.d.ts.map +0 -1
- package/lib/types/paired-model-catalog.js +0 -311
- package/src/mobile/PairRequiredView.test.tsx +0 -28
- package/src/mobile/PairRequiredView.tsx +0 -58
- package/src/mobile/api.ts +0 -229
- package/src/mobile/index.tsx +0 -41
- package/src/mobile/markdown.test.ts +0 -91
- package/src/mobile/markdown.ts +0 -291
- package/src/mobile/messages.test.ts +0 -402
- package/src/mobile/messages.ts +0 -685
- package/src/mobile/mobile-styles.ts +0 -1439
- package/src/mobile/mobile-theme.test.ts +0 -84
- package/src/mobile/mobile-theme.ts +0 -76
- package/src/mobile/mux.test.ts +0 -314
- package/src/mobile/mux.ts +0 -336
- package/src/mobile/pairing.ts +0 -77
- package/src/mobile/rpc.ts +0 -90
- package/src/mobile/theme-toggle.tsx +0 -47
- package/src/mobile/views/App.test.tsx +0 -62
- package/src/mobile/views/App.tsx +0 -213
- package/src/mobile/views/ChatView.stream.test.tsx +0 -268
- package/src/mobile/views/ChatView.test.tsx +0 -816
- package/src/mobile/views/ChatView.tsx +0 -1342
- package/src/mobile/views/SessionListView.test.tsx +0 -207
- package/src/mobile/views/SessionListView.tsx +0 -244
- package/src/mobile/views/WorkspaceView.test.tsx +0 -227
- package/src/mobile/views/WorkspaceView.tsx +0 -245
- package/src/mobile-api.ts +0 -418
- package/src/mobile-pending.ts +0 -88
- package/src/mobile-pwa.ts +0 -19
- package/src/mobile-routes.ts +0 -231
- package/src/paired-model-catalog.ts +0 -272
package/src/remote-methods.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Remote desktop channel constants — SDK-independent so tests and the
|
|
3
3
|
* client half can pin them without importing the host SDK graph.
|
|
4
|
+
*
|
|
5
|
+
* Access model on the 0.1.2-alpha.2 line: the host /api surface has no
|
|
6
|
+
* per-method privilege pin — the "configuration plane is local" behavior
|
|
7
|
+
* lives in the browser (client plugins branch on connection.isLoopback), and
|
|
8
|
+
* the paired remote desktop flips into host mode via the transport hook
|
|
9
|
+
* (ownsHost) while every call rides this gated channel as a loopback-shaped
|
|
10
|
+
* request. A paired device is therefore a full-control credential by
|
|
11
|
+
* design; the only paths that stay physically local are the control planes
|
|
12
|
+
* below (pairing control, self-update, plugin install/remove, host power).
|
|
4
13
|
*/
|
|
5
14
|
|
|
6
15
|
/** Gated mirror of same-origin fenced paths (`/remote` + original pathname). */
|
|
@@ -9,10 +18,17 @@ export const REMOTE_PREFIX = '/remote'
|
|
|
9
18
|
/** Connection-plugin method prefix under the gated channel. */
|
|
10
19
|
export const REMOTE_API_PREFIX = `${REMOTE_PREFIX}/api`
|
|
11
20
|
|
|
12
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* The gated mirrors of the official client WebSocket paths. On the pinned
|
|
23
|
+
* 0.1.2-alpha.2 line the client opens ONE persistent stream socket — the
|
|
24
|
+
* Typert gateway mux at `/api/remote.mux` — and every Remote stream
|
|
25
|
+
* (workspace follow, session feed, ...) rides it. If that socket is not
|
|
26
|
+
* rewritten onto the gated channel the phone's streams die at the connection
|
|
27
|
+
* fence and the UI shows an empty workspace/session mirror, so the mux is
|
|
28
|
+
* the one path that must never be missing here.
|
|
29
|
+
*/
|
|
13
30
|
export const REMOTE_API_PATHS = {
|
|
14
|
-
mux: `${REMOTE_API_PREFIX}/
|
|
15
|
-
host: `${REMOTE_API_PREFIX}/events.host`,
|
|
31
|
+
mux: `${REMOTE_API_PREFIX}/remote.mux`,
|
|
16
32
|
} as const
|
|
17
33
|
|
|
18
34
|
/**
|
|
@@ -21,7 +37,6 @@ export const REMOTE_API_PATHS = {
|
|
|
21
37
|
*/
|
|
22
38
|
export const REMOTE_UPGRADE_PATHS = [
|
|
23
39
|
REMOTE_API_PATHS.mux,
|
|
24
|
-
REMOTE_API_PATHS.host,
|
|
25
40
|
`${REMOTE_PREFIX}/sidebar/ws/terminal`,
|
|
26
41
|
`${REMOTE_PREFIX}/sidebar/ws/agent-terminals`,
|
|
27
42
|
`${REMOTE_API_PREFIX}/dsh-ssh/terminal`,
|
|
@@ -33,30 +48,46 @@ export const PLUGIN_MANAGER_PATH = '/api/plugin-manager'
|
|
|
33
48
|
/** Desktop-launcher HTTP prefix: shortcut create and host shutdown stay physically local. */
|
|
34
49
|
export const DESKTOP_LAUNCHER_PATH = '/api/dsh-desktop-launcher'
|
|
35
50
|
|
|
36
|
-
/** Family settings-bridge HTTP prefix
|
|
51
|
+
/** Family settings-bridge HTTP prefix — re-exposed to paired devices (plain settings parity). */
|
|
37
52
|
export const WEB_UI_SETTINGS_BRIDGE_PATH = '/api/dsh-web-ui-settings'
|
|
38
53
|
|
|
39
54
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
55
|
+
* The cookieless device credential: the boot patch reads the device id from
|
|
56
|
+
* the /pair-app URL, keeps it in sessionStorage, and attaches it to every
|
|
57
|
+
* gated HTTP call as this header (and to WebSocket upgrades as the `device`
|
|
58
|
+
* query parameter - WS handshakes cannot carry headers from the Web API).
|
|
59
|
+
* The channel gate accepts it exactly like the device cookie, so the mobile
|
|
60
|
+
* flow works even with browser cookies fully blocked; the cookie remains the
|
|
61
|
+
* primary credential on normal browsers.
|
|
62
|
+
*/
|
|
63
|
+
export const REMOTE_DEVICE_HEADER = 'x-dsh-remote-device'
|
|
64
|
+
export const REMOTE_DEVICE_QUERY = 'device'
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Path prefixes that stay physically local even for a paired device. A
|
|
68
|
+
* paired remote desktop may use the full host API (chat, sessions,
|
|
69
|
+
* settings, credentials, presets — it is a full-control credential), but it
|
|
70
|
+
* must not reach the machine-control planes: pairing control itself, the
|
|
71
|
+
* dsh-web self-update installer, plugin install/remove, and desktop
|
|
72
|
+
* launcher actions (host shutdown, shortcuts).
|
|
73
|
+
*/
|
|
74
|
+
export const LOCAL_ONLY_PREFIXES: readonly string[] = [
|
|
75
|
+
'/api/pair',
|
|
76
|
+
'/api/update',
|
|
77
|
+
PLUGIN_MANAGER_PATH,
|
|
78
|
+
DESKTOP_LAUNCHER_PATH,
|
|
79
|
+
] as const
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether a paired inner path must stay physically local.
|
|
83
|
+
* @param innerPath - the rewritten inner path (e.g. `/api/session.list`).
|
|
84
|
+
* @returns a denial message, or undefined when the path may be proxied.
|
|
45
85
|
*/
|
|
46
|
-
export
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
'settings.openDocument',
|
|
55
|
-
'settings.update',
|
|
56
|
-
'settings.replace',
|
|
57
|
-
'settings.mutate',
|
|
58
|
-
'credentials.describe',
|
|
59
|
-
'credentials.set',
|
|
60
|
-
'credentials.unset',
|
|
61
|
-
'llm.discoverModels',
|
|
62
|
-
])
|
|
86
|
+
export function localOnlyDenial(innerPath: string): string | undefined {
|
|
87
|
+
for (const prefix of LOCAL_ONLY_PREFIXES) {
|
|
88
|
+
if (innerPath === prefix || innerPath.startsWith(`${prefix}/`)) {
|
|
89
|
+
return `${prefix.slice(1)} stays physically local and stays unreachable from a paired remote desktop`
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return undefined
|
|
93
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote-presence to pet-visibility link (user requirement): while at least
|
|
3
|
+
* one paired device is online (an active phone mirror session), the
|
|
4
|
+
* host-global pet is hidden through the pet's OWN hide switch (the
|
|
5
|
+
* PetService.setVisible RPC, the same one the pet's hide button uses);
|
|
6
|
+
* once the last device stays offline past a grace window, the pet is
|
|
7
|
+
* restored. The pet plugin is optional: without it (or on any pet failure)
|
|
8
|
+
* the link is a no-op - the remote-control feature never depends on the
|
|
9
|
+
* pet being installed.
|
|
10
|
+
*
|
|
11
|
+
* The visibility is host-global by design (the pet is host-global, no
|
|
12
|
+
* session dimension): the desktop and the phone share one pet, so the link
|
|
13
|
+
* hides it while the operator is away on a phone mirror and restores it
|
|
14
|
+
* when the mirror ends. A manual user hide is respected: if the pet is
|
|
15
|
+
* already hidden when the first device comes online, nothing is recorded
|
|
16
|
+
* and nothing is restored later.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** The pet service seam the link needs (structural slice, no package edge). */
|
|
20
|
+
export interface PresencePetSeam {
|
|
21
|
+
/** RPC: show or hide the pet. */
|
|
22
|
+
setVisible(visible: boolean): Promise<unknown>
|
|
23
|
+
/** Read the current display state (the visibility the user sees). */
|
|
24
|
+
state(): Promise<{ display?: { visible?: boolean } }>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Dependencies of the presence link (all injectable for tests). */
|
|
28
|
+
export interface PresencePetDeps {
|
|
29
|
+
/** Subscribe to pairing snapshot changes (fires with every state change). */
|
|
30
|
+
onState(listener: (snapshot: { phase: string; onlineCount: number }) => void): () => void
|
|
31
|
+
/** Resolve the pet service seam (undefined when the pet plugin is absent). */
|
|
32
|
+
pet(): PresencePetSeam | undefined
|
|
33
|
+
/** Grace before restoring the pet after the last device went offline. */
|
|
34
|
+
restoreAfterMs?: number
|
|
35
|
+
timers?: {
|
|
36
|
+
setTimeout(fn: () => void, ms: number): unknown
|
|
37
|
+
clearTimeout(t: unknown): void
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Default restore grace: the presence sweep flips a device offline after
|
|
42
|
+
* ~25 s, and a phone that was briefly backgrounded should not flicker the
|
|
43
|
+
* pet; 2 minutes keeps the restore stable. */
|
|
44
|
+
const DEFAULT_RESTORE_AFTER_MS = 120_000
|
|
45
|
+
|
|
46
|
+
const nodeTimers = { setTimeout, clearTimeout }
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Start the presence link. Returns the disposer (withdraws the state
|
|
50
|
+
* subscription and cancels a pending restore).
|
|
51
|
+
* @param deps - pairing stream + pet seam (+ test seams).
|
|
52
|
+
*/
|
|
53
|
+
export function startRemotePresencePet(deps: PresencePetDeps): () => void {
|
|
54
|
+
const restoreAfterMs = deps.restoreAfterMs ?? DEFAULT_RESTORE_AFTER_MS
|
|
55
|
+
const timers = deps.timers ?? nodeTimers
|
|
56
|
+
let previousOnline = false
|
|
57
|
+
let hiddenByRemote = false
|
|
58
|
+
let restoreTimer: unknown | undefined
|
|
59
|
+
let disposed = false
|
|
60
|
+
|
|
61
|
+
const clearRestore = (): void => {
|
|
62
|
+
if (restoreTimer !== undefined) {
|
|
63
|
+
timers.clearTimeout(restoreTimer)
|
|
64
|
+
restoreTimer = undefined
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const scheduleRestore = (): void => {
|
|
69
|
+
clearRestore()
|
|
70
|
+
restoreTimer = timers.setTimeout(() => {
|
|
71
|
+
restoreTimer = undefined
|
|
72
|
+
restoreAfterTick()
|
|
73
|
+
}, restoreAfterMs)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const restoreAfterTick = (): void => {
|
|
77
|
+
if (disposed || !hiddenByRemote) return
|
|
78
|
+
const pet = deps.pet()
|
|
79
|
+
if (pet === undefined) return
|
|
80
|
+
hiddenByRemote = false
|
|
81
|
+
void pet.setVisible(true).catch(() => {
|
|
82
|
+
// The pet bridge failed (e.g. the pet host was mid-reload): the pet
|
|
83
|
+
// stays whatever it is; the next connect/disconnect cycle re-evaluates.
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const hideForRemote = (): void => {
|
|
88
|
+
if (hiddenByRemote) return
|
|
89
|
+
const pet = deps.pet()
|
|
90
|
+
if (pet === undefined) return
|
|
91
|
+
void pet.state().then(
|
|
92
|
+
(value) => {
|
|
93
|
+
if (disposed || hiddenByRemote) return
|
|
94
|
+
const visible = value.display?.visible
|
|
95
|
+
if (visible !== true) return
|
|
96
|
+
hiddenByRemote = true
|
|
97
|
+
return pet.setVisible(false).catch(() => {
|
|
98
|
+
// Hide failed: leave the flag unset so a later transition retries.
|
|
99
|
+
hiddenByRemote = false
|
|
100
|
+
})
|
|
101
|
+
},
|
|
102
|
+
() => {
|
|
103
|
+
// The state read failed; do not record a hide we never performed.
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const handle = (snapshot: { phase: string; onlineCount: number }): void => {
|
|
109
|
+
const online = snapshot.phase === 'connected' && snapshot.onlineCount > 0
|
|
110
|
+
if (online && !previousOnline) {
|
|
111
|
+
cancelRestoreAndClear()
|
|
112
|
+
hideForRemote()
|
|
113
|
+
} else if (!online && previousOnline) {
|
|
114
|
+
scheduleRestore()
|
|
115
|
+
}
|
|
116
|
+
previousOnline = online
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const cancelRestoreAndClear = (): void => {
|
|
120
|
+
clearRestore()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const unsubscribe = deps.onState(handle)
|
|
124
|
+
return () => {
|
|
125
|
+
disposed = true
|
|
126
|
+
unsubscribe()
|
|
127
|
+
clearRestore()
|
|
128
|
+
}
|
|
129
|
+
}
|