yaver-feedback-react-native 0.7.7 → 0.7.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/dist/Discovery.js +46 -31
- package/dist/MachinePickerScreen.js +18 -0
- package/dist/PairDeviceModal.d.ts +28 -0
- package/dist/PairDeviceModal.js +198 -0
- package/dist/_core/device.d.ts +79 -0
- package/dist/_core/device.js +310 -0
- package/dist/_core/index.d.ts +1 -0
- package/dist/_core/index.js +1 -0
- package/dist/deviceDedup.d.ts +13 -37
- package/dist/deviceDedup.js +19 -169
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/src/Discovery.ts +13 -30
- package/src/MachinePickerScreen.tsx +22 -0
- package/src/PairDeviceModal.tsx +228 -0
- package/src/_core/device.ts +352 -0
- package/src/_core/index.ts +1 -0
- package/src/deviceDedup.ts +26 -166
- package/src/index.ts +2 -0
package/src/deviceDedup.ts
CHANGED
|
@@ -1,185 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Re-export the canonical dedup / freshness / race-probe helpers from
|
|
3
|
+
* @yaver/client-core (mirrored into `./_core/`).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* The SDK previously carried its own ~180-line copy of
|
|
6
|
+
* `collapseAliasDevices` ported by hand from mobile's
|
|
7
|
+
* DeviceContext.tsx. The copies drifted on `hwid` strong-identity,
|
|
8
|
+
* `runners` / `local` field preservation, and a few other subtle
|
|
9
|
+
* merge rules. Canonicalising via client-core eliminates that drift
|
|
10
|
+
* class — mobile's test case automatically covers the SDK's dedup
|
|
11
|
+
* behaviour too.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* mint a new hwid
|
|
14
|
-
* 3. Endpoint key (host:port)
|
|
15
|
-
*
|
|
16
|
-
* When collapsing, `mergeDeviceEntries` prefers: authenticated over
|
|
17
|
-
* needsAuth, online over offline, freshest lastHeartbeat.
|
|
13
|
+
* The shared module operates on a `CoreDevice` shape. The SDK's
|
|
14
|
+
* `RemoteDevice` is structurally compatible (same field names, same
|
|
15
|
+
* types), so the casts here are a no-op at runtime.
|
|
18
16
|
*/
|
|
19
17
|
|
|
18
|
+
import {
|
|
19
|
+
collapseDevices,
|
|
20
|
+
isDeviceFresh as isCoreDeviceFresh,
|
|
21
|
+
pickTargetDevice as pickCoreTargetDevice,
|
|
22
|
+
type CoreDevice,
|
|
23
|
+
} from './_core/device';
|
|
20
24
|
import type { RemoteDevice } from './auth';
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
return String(name || '').trim().toLowerCase().replace(/\.local$/i, '');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function normalizedHost(host: string | undefined): string {
|
|
27
|
-
return String(host || '').trim().toLowerCase().replace(/\.local$/i, '');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function identityKey(d: RemoteDevice): string {
|
|
31
|
-
if (d.hwid) return `hwid:${d.hwid}`;
|
|
32
|
-
if (d.publicKey) return `pub:${d.publicKey}`;
|
|
33
|
-
if (d.isGuest) {
|
|
34
|
-
const scope = d.hostEmail || d.hostName || 'guest';
|
|
35
|
-
return `guest:${scope}:${d.deviceId || d.name}`;
|
|
36
|
-
}
|
|
37
|
-
const n = normalizedName(d.name);
|
|
38
|
-
const os = String(d.platform || '').trim().toLowerCase();
|
|
39
|
-
if (n && os) return `host:${os}:${n}`;
|
|
40
|
-
if (d.deviceId) return `id:${d.deviceId}`;
|
|
41
|
-
return `name:${d.name}`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function aliasKey(d: RemoteDevice): string | null {
|
|
45
|
-
if (d.isGuest) return null;
|
|
46
|
-
const n = normalizedName(d.name);
|
|
47
|
-
const os = String(d.platform || '').trim().toLowerCase();
|
|
48
|
-
if (!n || !os) return null;
|
|
49
|
-
return `${os}:${n}`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function endpointKey(d: RemoteDevice): string | null {
|
|
53
|
-
if (d.isGuest) return null;
|
|
54
|
-
const h = normalizedHost(d.quicHost);
|
|
55
|
-
if (!h) return null;
|
|
56
|
-
return `${h}:${d.quicPort || 0}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function mergeEntries(existing: RemoteDevice, incoming: RemoteDevice): RemoteDevice {
|
|
60
|
-
const incomingWins =
|
|
61
|
-
(!!existing.needsAuth && !incoming.needsAuth) ||
|
|
62
|
-
(incoming.lastHeartbeat || 0) > (existing.lastHeartbeat || 0) ||
|
|
63
|
-
(!!incoming.isOnline && !existing.isOnline);
|
|
64
|
-
const base = incomingWins ? incoming : existing;
|
|
65
|
-
const other = incomingWins ? existing : incoming;
|
|
66
|
-
return {
|
|
67
|
-
...other,
|
|
68
|
-
...base,
|
|
69
|
-
quicHost: base.quicHost || other.quicHost,
|
|
70
|
-
quicPort: base.quicPort || other.quicPort,
|
|
71
|
-
isOnline: base.isOnline || other.isOnline,
|
|
72
|
-
runnerDown: base.runnerDown && other.runnerDown,
|
|
73
|
-
publicKey: base.publicKey || other.publicKey,
|
|
74
|
-
lastHeartbeat: Math.max(existing.lastHeartbeat || 0, incoming.lastHeartbeat || 0),
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// When two rows share the same alias key (hostname + OS) but differ on
|
|
79
|
-
// hwid/publicKey, pick the active one over the stale needs-auth leftover.
|
|
80
|
-
function pickActiveOverStaleNeedsAuth(a: RemoteDevice, b: RemoteDevice): RemoteDevice | null {
|
|
81
|
-
const aDead = a.needsAuth && !a.isOnline;
|
|
82
|
-
const bDead = b.needsAuth && !b.isOnline;
|
|
83
|
-
const aLive = !a.needsAuth && a.isOnline;
|
|
84
|
-
const bLive = !b.needsAuth && b.isOnline;
|
|
85
|
-
if (aDead && bLive) return b;
|
|
86
|
-
if (bDead && aLive) return a;
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
26
|
+
export { HEARTBEAT_STALE_MS } from './_core/constants';
|
|
89
27
|
|
|
90
|
-
/**
|
|
91
|
-
* Collapse a Convex device list so each physical machine appears once.
|
|
92
|
-
* Safe on an empty list; idempotent on an already-deduped list.
|
|
93
|
-
*/
|
|
94
28
|
export function collapseRemoteDevices(devices: RemoteDevice[]): RemoteDevice[] {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const byIdentity = new Map<string, RemoteDevice>();
|
|
98
|
-
for (const d of devices) {
|
|
99
|
-
const k = identityKey(d);
|
|
100
|
-
const prev = byIdentity.get(k);
|
|
101
|
-
byIdentity.set(k, prev ? mergeEntries(prev, d) : d);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const byAlias = new Map<string, RemoteDevice>();
|
|
105
|
-
for (const d of byIdentity.values()) {
|
|
106
|
-
const k = aliasKey(d);
|
|
107
|
-
if (!k) {
|
|
108
|
-
byAlias.set(`id:${d.deviceId}`, d);
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
const prev = byAlias.get(k);
|
|
112
|
-
if (!prev) {
|
|
113
|
-
byAlias.set(k, d);
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
const strongIdentityConflict =
|
|
117
|
-
!!prev.publicKey && !!d.publicKey && prev.publicKey !== d.publicKey;
|
|
118
|
-
if (strongIdentityConflict) {
|
|
119
|
-
const winner = pickActiveOverStaleNeedsAuth(prev, d);
|
|
120
|
-
if (winner) {
|
|
121
|
-
byAlias.set(k, winner);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
byAlias.set(k, mergeEntries(prev, d));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const byEndpoint = new Map<string, RemoteDevice>();
|
|
129
|
-
for (const d of byAlias.values()) {
|
|
130
|
-
const k = endpointKey(d);
|
|
131
|
-
if (!k) {
|
|
132
|
-
byEndpoint.set(`id:${d.deviceId}`, d);
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
const prev = byEndpoint.get(k);
|
|
136
|
-
byEndpoint.set(k, prev ? mergeEntries(prev, d) : d);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return [...byEndpoint.values()];
|
|
29
|
+
return collapseDevices(devices as unknown as CoreDevice[]) as unknown as RemoteDevice[];
|
|
140
30
|
}
|
|
141
31
|
|
|
142
|
-
/**
|
|
143
|
-
* Threshold for "online" based on heartbeat age, in milliseconds.
|
|
144
|
-
* Re-exported from `@yaver/client-core` so mobile + SDK + backend
|
|
145
|
-
* agree on one number at all times.
|
|
146
|
-
*/
|
|
147
|
-
import { HEARTBEAT_STALE_MS } from './_core/constants';
|
|
148
|
-
export { HEARTBEAT_STALE_MS };
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Returns a freshness flag consistent with the mobile app. A device is
|
|
152
|
-
* "fresh" when it was online per Convex AND its heartbeat is within
|
|
153
|
-
* `HEARTBEAT_STALE_MS`.
|
|
154
|
-
*/
|
|
155
32
|
export function isDeviceFresh(d: RemoteDevice): boolean {
|
|
156
|
-
|
|
157
|
-
if (!d.lastHeartbeat) return true;
|
|
158
|
-
return Date.now() - d.lastHeartbeat < HEARTBEAT_STALE_MS;
|
|
33
|
+
return isCoreDeviceFresh(d as unknown as CoreDevice);
|
|
159
34
|
}
|
|
160
35
|
|
|
161
|
-
/**
|
|
162
|
-
* Pick the best candidate for an auto-connect attempt. Preference:
|
|
163
|
-
* 1. matches the preferred deviceId when supplied + still fresh
|
|
164
|
-
* 2. fresh (online + recent heartbeat) + has a quicHost
|
|
165
|
-
* 3. online + has a quicHost
|
|
166
|
-
* 4. first with a quicHost
|
|
167
|
-
*/
|
|
168
36
|
export function pickTargetDevice(
|
|
169
37
|
devices: RemoteDevice[],
|
|
170
38
|
preferredDeviceId?: string,
|
|
171
39
|
): RemoteDevice | null {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (preferred && isDeviceFresh(preferred)) return preferred;
|
|
178
|
-
if (preferred) return preferred;
|
|
179
|
-
}
|
|
180
|
-
const fresh = devices.find((d) => isDeviceFresh(d) && d.quicHost);
|
|
181
|
-
if (fresh) return fresh;
|
|
182
|
-
const online = devices.find((d) => d.isOnline && d.quicHost);
|
|
183
|
-
if (online) return online;
|
|
184
|
-
return devices.find((d) => d.quicHost) || devices[0] || null;
|
|
40
|
+
const pick = pickCoreTargetDevice(
|
|
41
|
+
devices as unknown as CoreDevice[],
|
|
42
|
+
preferredDeviceId,
|
|
43
|
+
);
|
|
44
|
+
return (pick ?? null) as RemoteDevice | null;
|
|
185
45
|
}
|
package/src/index.ts
CHANGED
|
@@ -39,6 +39,8 @@ export { YaverLoginScreen } from './LoginScreen';
|
|
|
39
39
|
export type { YaverLoginScreenProps } from './LoginScreen';
|
|
40
40
|
export { YaverMachinePickerScreen } from './MachinePickerScreen';
|
|
41
41
|
export type { YaverMachinePickerProps } from './MachinePickerScreen';
|
|
42
|
+
export { PairDeviceModal } from './PairDeviceModal';
|
|
43
|
+
export type { PairDeviceModalProps } from './PairDeviceModal';
|
|
42
44
|
export { AuthOverlay } from './AuthOverlay';
|
|
43
45
|
export { ShakeDetector } from './ShakeDetector';
|
|
44
46
|
export { FloatingButton } from './FloatingButton';
|