@elizaos/plugin-phone 2.0.3-beta.2 → 2.0.3-beta.3
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/companion/components/Chat.d.ts +14 -0
- package/dist/companion/components/Chat.d.ts.map +1 -0
- package/dist/companion/components/Chat.js +195 -0
- package/dist/companion/components/Chat.js.map +1 -0
- package/dist/companion/components/Pairing.d.ts +9 -0
- package/dist/companion/components/Pairing.d.ts.map +1 -0
- package/dist/companion/components/Pairing.js +175 -0
- package/dist/companion/components/Pairing.js.map +1 -0
- package/dist/companion/components/PhoneCompanionApp.d.ts +9 -0
- package/dist/companion/components/PhoneCompanionApp.d.ts.map +1 -0
- package/dist/companion/components/PhoneCompanionApp.js +174 -0
- package/dist/companion/components/PhoneCompanionApp.js.map +1 -0
- package/dist/companion/components/RemoteSession.d.ts +9 -0
- package/dist/companion/components/RemoteSession.d.ts.map +1 -0
- package/dist/companion/components/RemoteSession.js +382 -0
- package/dist/companion/components/RemoteSession.js.map +1 -0
- package/dist/companion/components/index.d.ts +5 -0
- package/dist/companion/components/index.d.ts.map +1 -0
- package/dist/companion/components/index.js +11 -0
- package/dist/companion/components/index.js.map +1 -0
- package/dist/companion/index.d.ts +14 -0
- package/dist/companion/index.d.ts.map +1 -0
- package/dist/companion/index.js +12 -0
- package/dist/companion/index.js.map +1 -0
- package/dist/companion/services/eliza-intent.d.ts +63 -0
- package/dist/companion/services/eliza-intent.d.ts.map +1 -0
- package/dist/companion/services/eliza-intent.js +52 -0
- package/dist/companion/services/eliza-intent.js.map +1 -0
- package/dist/companion/services/env.d.ts +11 -0
- package/dist/companion/services/env.d.ts.map +1 -0
- package/dist/companion/services/env.js +20 -0
- package/dist/companion/services/env.js.map +1 -0
- package/dist/companion/services/index.d.ts +14 -0
- package/dist/companion/services/index.d.ts.map +1 -0
- package/dist/companion/services/index.js +29 -0
- package/dist/companion/services/index.js.map +1 -0
- package/dist/companion/services/intent-bridge.d.ts +11 -0
- package/dist/companion/services/intent-bridge.d.ts.map +1 -0
- package/dist/companion/services/intent-bridge.js +15 -0
- package/dist/companion/services/intent-bridge.js.map +1 -0
- package/dist/companion/services/logger.d.ts +14 -0
- package/dist/companion/services/logger.d.ts.map +1 -0
- package/dist/companion/services/logger.js +50 -0
- package/dist/companion/services/logger.js.map +1 -0
- package/dist/companion/services/navigation.d.ts +17 -0
- package/dist/companion/services/navigation.d.ts.map +1 -0
- package/dist/companion/services/navigation.js +104 -0
- package/dist/companion/services/navigation.js.map +1 -0
- package/dist/companion/services/push.d.ts +27 -0
- package/dist/companion/services/push.d.ts.map +1 -0
- package/dist/companion/services/push.js +101 -0
- package/dist/companion/services/push.js.map +1 -0
- package/dist/companion/services/session-client.d.ts +114 -0
- package/dist/companion/services/session-client.d.ts.map +1 -0
- package/dist/companion/services/session-client.js +197 -0
- package/dist/companion/services/session-client.js.map +1 -0
- package/dist/components/PhoneAppView.d.ts +18 -0
- package/dist/components/PhoneAppView.d.ts.map +1 -0
- package/dist/components/PhoneAppView.helpers.d.ts +12 -0
- package/dist/components/PhoneAppView.helpers.d.ts.map +1 -0
- package/dist/components/PhoneAppView.helpers.js +44 -0
- package/dist/components/PhoneAppView.helpers.js.map +1 -0
- package/dist/components/PhoneAppView.interact.d.ts +2 -0
- package/dist/components/PhoneAppView.interact.d.ts.map +1 -0
- package/dist/components/PhoneAppView.interact.js +63 -0
- package/dist/components/PhoneAppView.interact.js.map +1 -0
- package/dist/components/PhoneAppView.js +566 -0
- package/dist/components/PhoneAppView.js.map +1 -0
- package/dist/components/PhoneSpatialView.d.ts +43 -0
- package/dist/components/PhoneSpatialView.d.ts.map +1 -0
- package/dist/components/PhoneSpatialView.js +180 -0
- package/dist/components/PhoneSpatialView.js.map +1 -0
- package/dist/components/PhoneView.d.ts +13 -0
- package/dist/components/PhoneView.d.ts.map +1 -0
- package/dist/components/PhoneView.js +161 -0
- package/dist/components/PhoneView.js.map +1 -0
- package/dist/components/phone-app.d.ts +13 -0
- package/dist/components/phone-app.d.ts.map +1 -0
- package/dist/components/phone-app.js +20 -0
- package/dist/components/phone-app.js.map +1 -0
- package/dist/components/phone-view-bundle.d.ts +3 -0
- package/dist/components/phone-view-bundle.d.ts.map +1 -0
- package/dist/components/phone-view-bundle.js +7 -0
- package/dist/components/phone-view-bundle.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +17 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +48 -0
- package/dist/plugin.js.map +1 -0
- package/dist/providers/call-log.d.ts +11 -0
- package/dist/providers/call-log.d.ts.map +1 -0
- package/dist/providers/call-log.js +66 -0
- package/dist/providers/call-log.js.map +1 -0
- package/dist/register-companion-page.d.ts +13 -0
- package/dist/register-companion-page.d.ts.map +1 -0
- package/dist/register-companion-page.js +12 -0
- package/dist/register-companion-page.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +21 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +9 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +11 -0
- package/dist/register.js.map +1 -0
- package/dist/twilio.d.ts +33 -0
- package/dist/twilio.d.ts.map +1 -0
- package/dist/twilio.js +238 -0
- package/dist/twilio.js.map +1 -0
- package/dist/ui.d.ts +8 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +23 -0
- package/dist/ui.js.map +1 -0
- package/dist/views/bundle.js +407 -0
- package/dist/views/bundle.js.map +1 -0
- package/dist/views/dist-Cd2YtKy4.js +270 -0
- package/dist/views/dist-Cd2YtKy4.js.map +1 -0
- package/dist/views/web-TGRkTsa8.js +58 -0
- package/dist/views/web-TGRkTsa8.js.map +1 -0
- package/package.json +9 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RemoteSession.d.ts","sourceRoot":"","sources":["../../../src/companion/components/RemoteSession.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AACf,OAAO,EAEL,KAAK,cAAc,EAIpB,MAAM,aAAa,CAAC;AAErB,UAAU,kBAAkB;IAC1B,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,IAAI,IAAI,CAAC;CAChB;AAiKD,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,MAAM,GACP,EAAE,kBAAkB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA4NxC"}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import {
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState
|
|
8
|
+
} from "react";
|
|
9
|
+
import {
|
|
10
|
+
logger,
|
|
11
|
+
SessionClient,
|
|
12
|
+
touchToInput
|
|
13
|
+
} from "../services/index.js";
|
|
14
|
+
const PULL_TO_REFRESH_THRESHOLD_PX = 80;
|
|
15
|
+
const PRIVATE_IPV6_PREFIXES = ["fe80:", "fec0:", "fc", "fd"];
|
|
16
|
+
function parseIpv4Address(address) {
|
|
17
|
+
const parts = address.split(".");
|
|
18
|
+
if (parts.length !== 4) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const numbers = parts.map((part) => Number.parseInt(part, 10));
|
|
22
|
+
if (numbers.some((value) => Number.isNaN(value) || value < 0 || value > 255)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return numbers;
|
|
26
|
+
}
|
|
27
|
+
function parseMappedIpv6Address(mappedAddress) {
|
|
28
|
+
if (mappedAddress.includes(".")) {
|
|
29
|
+
return parseIpv4Address(mappedAddress);
|
|
30
|
+
}
|
|
31
|
+
const parts = mappedAddress.split(":").filter(Boolean);
|
|
32
|
+
if (parts.length === 1) {
|
|
33
|
+
const value2 = Number.parseInt(parts[0], 16);
|
|
34
|
+
if (Number.isNaN(value2) || value2 < 0 || value2 > 4294967295) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return [
|
|
38
|
+
value2 >>> 24 & 255,
|
|
39
|
+
value2 >>> 16 & 255,
|
|
40
|
+
value2 >>> 8 & 255,
|
|
41
|
+
value2 & 255
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
if (parts.length !== 2) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const high = Number.parseInt(parts[0], 16);
|
|
48
|
+
const low = Number.parseInt(parts[1], 16);
|
|
49
|
+
if (Number.isNaN(high) || Number.isNaN(low) || high < 0 || low < 0 || high > 65535 || low > 65535) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const value = (high << 16) + low;
|
|
53
|
+
return [
|
|
54
|
+
value >>> 24 & 255,
|
|
55
|
+
value >>> 16 & 255,
|
|
56
|
+
value >>> 8 & 255,
|
|
57
|
+
value & 255
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
function isPrivateIpv4Address(parts) {
|
|
61
|
+
const [octet1, octet2] = parts;
|
|
62
|
+
return octet1 === 0 || octet1 === 10 || octet1 === 127 || octet1 === 169 && octet2 === 254 || octet1 === 172 && octet2 >= 16 && octet2 <= 31 || octet1 === 192 && octet2 === 168 || octet1 === 100 && octet2 >= 64 && octet2 <= 127;
|
|
63
|
+
}
|
|
64
|
+
function isPrivateCompanionHost(host) {
|
|
65
|
+
let normalized = host.trim().toLowerCase();
|
|
66
|
+
if (normalized.startsWith("[") && normalized.endsWith("]")) {
|
|
67
|
+
normalized = normalized.slice(1, -1);
|
|
68
|
+
}
|
|
69
|
+
if (normalized.startsWith("::ffff:")) {
|
|
70
|
+
const mapped = parseMappedIpv6Address(normalized.slice("::ffff:".length));
|
|
71
|
+
return mapped !== null && isPrivateIpv4Address(mapped);
|
|
72
|
+
}
|
|
73
|
+
if (normalized.includes(":")) {
|
|
74
|
+
return normalized === "::" || normalized === "::1" || PRIVATE_IPV6_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
|
75
|
+
}
|
|
76
|
+
const ipv4 = parseIpv4Address(normalized);
|
|
77
|
+
return ipv4 !== null && isPrivateIpv4Address(ipv4);
|
|
78
|
+
}
|
|
79
|
+
function assertSafeCompanionIngressUrl(ingressUrl) {
|
|
80
|
+
let parsed;
|
|
81
|
+
try {
|
|
82
|
+
parsed = new URL(ingressUrl);
|
|
83
|
+
} catch {
|
|
84
|
+
throw new Error("Companion ingress URL is not a valid absolute URL");
|
|
85
|
+
}
|
|
86
|
+
const protocol = parsed.protocol.toLowerCase();
|
|
87
|
+
if (protocol !== "wss:" && protocol !== "ws:") {
|
|
88
|
+
throw new Error("Companion ingress must use wss: or ws: (WebSocket) URL");
|
|
89
|
+
}
|
|
90
|
+
if (parsed.hostname.length === 0) {
|
|
91
|
+
throw new Error("Companion ingress URL is missing a host");
|
|
92
|
+
}
|
|
93
|
+
if (parsed.username !== "" || parsed.password !== "") {
|
|
94
|
+
throw new Error("Companion ingress URL must not embed credentials");
|
|
95
|
+
}
|
|
96
|
+
const host = parsed.hostname.toLowerCase();
|
|
97
|
+
if (host === "169.254.169.254" || host === "metadata.google.internal") {
|
|
98
|
+
throw new Error("Companion ingress host is not allowed");
|
|
99
|
+
}
|
|
100
|
+
if (protocol === "ws:") {
|
|
101
|
+
const allowPlaintextWs = host === "localhost" || host === "127.0.0.1" || host === "::1" || isPrivateCompanionHost(host);
|
|
102
|
+
if (!allowPlaintextWs) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
"ws:// is only allowed on localhost or private networks; use wss:// for this host"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return parsed;
|
|
109
|
+
}
|
|
110
|
+
function buildViewerUrl(ingressUrl, payload) {
|
|
111
|
+
const viewerUrl = new URL(ingressUrl.toString());
|
|
112
|
+
viewerUrl.protocol = ingressUrl.protocol === "wss:" ? "https:" : "http:";
|
|
113
|
+
viewerUrl.pathname = ingressUrl.pathname.replace(/\/input\/?$/, "") || "/";
|
|
114
|
+
viewerUrl.pathname = `${viewerUrl.pathname.replace(/\/$/, "")}/vnc`;
|
|
115
|
+
viewerUrl.searchParams.set("token", payload.sessionToken);
|
|
116
|
+
viewerUrl.searchParams.set("agent", payload.agentId);
|
|
117
|
+
viewerUrl.hash = "";
|
|
118
|
+
return viewerUrl.toString();
|
|
119
|
+
}
|
|
120
|
+
function buildInputUrl(ingressUrl) {
|
|
121
|
+
const inputUrl = new URL(ingressUrl.toString());
|
|
122
|
+
if (!/\/input\/?$/.test(inputUrl.pathname)) {
|
|
123
|
+
inputUrl.pathname = `${inputUrl.pathname.replace(/\/$/, "")}/input`;
|
|
124
|
+
}
|
|
125
|
+
inputUrl.hash = "";
|
|
126
|
+
return inputUrl.toString();
|
|
127
|
+
}
|
|
128
|
+
function RemoteSession({
|
|
129
|
+
payload,
|
|
130
|
+
onExit
|
|
131
|
+
}) {
|
|
132
|
+
const [connState, setConnState] = useState("connecting");
|
|
133
|
+
const [reconnectNonce, setReconnectNonce] = useState(0);
|
|
134
|
+
const [pullPx, setPullPx] = useState(0);
|
|
135
|
+
const pullPxRef = useRef(0);
|
|
136
|
+
const clientRef = useRef(null);
|
|
137
|
+
const surfaceRef = useRef(null);
|
|
138
|
+
const pointersRef = useRef(/* @__PURE__ */ new Map());
|
|
139
|
+
const gestureStartRef = useRef(null);
|
|
140
|
+
const completedPointersRef = useRef([]);
|
|
141
|
+
const sessionEndpoints = useMemo(() => {
|
|
142
|
+
try {
|
|
143
|
+
const ingressUrl = assertSafeCompanionIngressUrl(payload.ingressUrl);
|
|
144
|
+
return {
|
|
145
|
+
ok: true,
|
|
146
|
+
viewerUrl: buildViewerUrl(ingressUrl, payload),
|
|
147
|
+
inputUrl: buildInputUrl(ingressUrl)
|
|
148
|
+
};
|
|
149
|
+
} catch (cause) {
|
|
150
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
151
|
+
logger.error("[RemoteSession] rejected ingress URL", { message });
|
|
152
|
+
return { ok: false, error: message };
|
|
153
|
+
}
|
|
154
|
+
}, [payload]);
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!sessionEndpoints.ok) {
|
|
157
|
+
setConnState("error");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const client = new SessionClient();
|
|
161
|
+
clientRef.current = client;
|
|
162
|
+
const offState = client.on("state", (state) => {
|
|
163
|
+
if (state === "connecting") setConnState("connecting");
|
|
164
|
+
else if (state === "open") setConnState("open");
|
|
165
|
+
else if (state === "closed") setConnState("closed");
|
|
166
|
+
});
|
|
167
|
+
const offError = client.on("error", () => {
|
|
168
|
+
setConnState("error");
|
|
169
|
+
});
|
|
170
|
+
logger.info("[RemoteSession] connecting", {
|
|
171
|
+
agentId: payload.agentId,
|
|
172
|
+
attempt: reconnectNonce
|
|
173
|
+
});
|
|
174
|
+
client.connect(sessionEndpoints.inputUrl, payload.sessionToken);
|
|
175
|
+
return () => {
|
|
176
|
+
offState();
|
|
177
|
+
offError();
|
|
178
|
+
client.close();
|
|
179
|
+
clientRef.current = null;
|
|
180
|
+
};
|
|
181
|
+
}, [payload.agentId, payload.sessionToken, reconnectNonce, sessionEndpoints]);
|
|
182
|
+
const reconnect = useCallback(() => {
|
|
183
|
+
logger.info("[RemoteSession] reconnect requested", {});
|
|
184
|
+
setReconnectNonce((n) => n + 1);
|
|
185
|
+
}, []);
|
|
186
|
+
const onPointerDown = useCallback(
|
|
187
|
+
(event) => {
|
|
188
|
+
if (event.pointerType !== "touch") return;
|
|
189
|
+
const rect = surfaceRef.current?.getBoundingClientRect();
|
|
190
|
+
if (rect === void 0) return;
|
|
191
|
+
const sample = {
|
|
192
|
+
x: event.clientX - rect.left,
|
|
193
|
+
y: event.clientY - rect.top,
|
|
194
|
+
t: event.timeStamp,
|
|
195
|
+
pointerId: event.pointerId
|
|
196
|
+
};
|
|
197
|
+
pointersRef.current.set(event.pointerId, [sample]);
|
|
198
|
+
if (gestureStartRef.current === null) {
|
|
199
|
+
gestureStartRef.current = event.timeStamp;
|
|
200
|
+
}
|
|
201
|
+
event.target.setPointerCapture?.(event.pointerId);
|
|
202
|
+
},
|
|
203
|
+
[]
|
|
204
|
+
);
|
|
205
|
+
const onPointerMove = useCallback(
|
|
206
|
+
(event) => {
|
|
207
|
+
if (event.pointerType !== "touch") return;
|
|
208
|
+
const samples = pointersRef.current.get(event.pointerId);
|
|
209
|
+
if (samples === void 0) return;
|
|
210
|
+
const rect = surfaceRef.current?.getBoundingClientRect();
|
|
211
|
+
if (rect === void 0) return;
|
|
212
|
+
samples.push({
|
|
213
|
+
x: event.clientX - rect.left,
|
|
214
|
+
y: event.clientY - rect.top,
|
|
215
|
+
t: event.timeStamp,
|
|
216
|
+
pointerId: event.pointerId
|
|
217
|
+
});
|
|
218
|
+
if (pointersRef.current.size === 1 && samples.length > 1) {
|
|
219
|
+
const first = samples[0];
|
|
220
|
+
const last = samples[samples.length - 1];
|
|
221
|
+
if (first.y < 40 && last.y > first.y) {
|
|
222
|
+
const next = Math.min(
|
|
223
|
+
last.y - first.y,
|
|
224
|
+
PULL_TO_REFRESH_THRESHOLD_PX * 2
|
|
225
|
+
);
|
|
226
|
+
pullPxRef.current = next;
|
|
227
|
+
setPullPx(next);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
[]
|
|
232
|
+
);
|
|
233
|
+
const onPointerUp = useCallback(
|
|
234
|
+
(event) => {
|
|
235
|
+
if (event.pointerType !== "touch") return;
|
|
236
|
+
const samples = pointersRef.current.get(event.pointerId);
|
|
237
|
+
if (samples === void 0) return;
|
|
238
|
+
pointersRef.current.delete(event.pointerId);
|
|
239
|
+
if (pointersRef.current.size > 0) {
|
|
240
|
+
completedPointersRef.current.push(samples);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
completedPointersRef.current.push(samples);
|
|
244
|
+
const pointers = completedPointersRef.current.slice();
|
|
245
|
+
completedPointersRef.current = [];
|
|
246
|
+
gestureStartRef.current = null;
|
|
247
|
+
const pull = pullPxRef.current;
|
|
248
|
+
if (pull >= PULL_TO_REFRESH_THRESHOLD_PX) {
|
|
249
|
+
pullPxRef.current = 0;
|
|
250
|
+
setPullPx(0);
|
|
251
|
+
reconnect();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
pullPxRef.current = 0;
|
|
255
|
+
setPullPx(0);
|
|
256
|
+
const events = touchToInput({ pointers, ended: true });
|
|
257
|
+
const client = clientRef.current;
|
|
258
|
+
if (client === null) return;
|
|
259
|
+
for (const ev of events) client.sendInput(ev);
|
|
260
|
+
},
|
|
261
|
+
[reconnect]
|
|
262
|
+
);
|
|
263
|
+
const onTouchStart = useCallback((event) => {
|
|
264
|
+
event.preventDefault();
|
|
265
|
+
}, []);
|
|
266
|
+
return /* @__PURE__ */ jsxs("main", { style: styles.root, children: [
|
|
267
|
+
/* @__PURE__ */ jsxs("header", { style: styles.header, children: [
|
|
268
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onExit, style: styles.back, children: "Exit" }),
|
|
269
|
+
/* @__PURE__ */ jsx("span", { style: styles.status, children: !sessionEndpoints.ok ? sessionEndpoints.error : statusLabel(connState) }),
|
|
270
|
+
/* @__PURE__ */ jsx(
|
|
271
|
+
"button",
|
|
272
|
+
{
|
|
273
|
+
type: "button",
|
|
274
|
+
onClick: reconnect,
|
|
275
|
+
disabled: !sessionEndpoints.ok,
|
|
276
|
+
style: styles.reconnect,
|
|
277
|
+
children: "Reconnect"
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
] }),
|
|
281
|
+
pullPx > 0 ? /* @__PURE__ */ jsx(
|
|
282
|
+
"div",
|
|
283
|
+
{
|
|
284
|
+
style: {
|
|
285
|
+
...styles.pull,
|
|
286
|
+
height: pullPx,
|
|
287
|
+
opacity: Math.min(pullPx / PULL_TO_REFRESH_THRESHOLD_PX, 1)
|
|
288
|
+
},
|
|
289
|
+
children: pullPx >= PULL_TO_REFRESH_THRESHOLD_PX ? "Release to reconnect" : "Pull to reconnect"
|
|
290
|
+
}
|
|
291
|
+
) : null,
|
|
292
|
+
/* @__PURE__ */ jsxs("div", { style: styles.viewerShell, children: [
|
|
293
|
+
/* @__PURE__ */ jsx(
|
|
294
|
+
"iframe",
|
|
295
|
+
{
|
|
296
|
+
title: "Remote desktop",
|
|
297
|
+
src: sessionEndpoints.ok ? sessionEndpoints.viewerUrl : "about:blank",
|
|
298
|
+
style: styles.iframe,
|
|
299
|
+
allow: "clipboard-read; clipboard-write",
|
|
300
|
+
sandbox: "allow-scripts allow-same-origin allow-forms"
|
|
301
|
+
}
|
|
302
|
+
),
|
|
303
|
+
/* @__PURE__ */ jsx(
|
|
304
|
+
"div",
|
|
305
|
+
{
|
|
306
|
+
ref: surfaceRef,
|
|
307
|
+
style: {
|
|
308
|
+
...styles.inputSurface,
|
|
309
|
+
pointerEvents: sessionEndpoints.ok ? "auto" : "none"
|
|
310
|
+
},
|
|
311
|
+
onPointerDown,
|
|
312
|
+
onPointerMove,
|
|
313
|
+
onPointerUp,
|
|
314
|
+
onPointerCancel: onPointerUp,
|
|
315
|
+
onTouchStart
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
] })
|
|
319
|
+
] });
|
|
320
|
+
}
|
|
321
|
+
function statusLabel(state) {
|
|
322
|
+
if (state === "connecting") return "Connecting...";
|
|
323
|
+
if (state === "open") return "Connected";
|
|
324
|
+
if (state === "error") return "Error";
|
|
325
|
+
return "Disconnected";
|
|
326
|
+
}
|
|
327
|
+
const styles = {
|
|
328
|
+
root: {
|
|
329
|
+
display: "flex",
|
|
330
|
+
flexDirection: "column",
|
|
331
|
+
height: "100%",
|
|
332
|
+
background: "#000"
|
|
333
|
+
},
|
|
334
|
+
header: {
|
|
335
|
+
display: "flex",
|
|
336
|
+
alignItems: "center",
|
|
337
|
+
justifyContent: "space-between",
|
|
338
|
+
padding: "12px 16px",
|
|
339
|
+
background: "#0a0a0a",
|
|
340
|
+
borderBottom: "1px solid #1f2937"
|
|
341
|
+
},
|
|
342
|
+
back: {
|
|
343
|
+
background: "transparent",
|
|
344
|
+
border: "none",
|
|
345
|
+
color: "#93c5fd",
|
|
346
|
+
fontSize: 16
|
|
347
|
+
},
|
|
348
|
+
status: { fontSize: 14, opacity: 0.8 },
|
|
349
|
+
reconnect: {
|
|
350
|
+
background: "transparent",
|
|
351
|
+
border: "1px solid #374151",
|
|
352
|
+
color: "#e5e7eb",
|
|
353
|
+
borderRadius: 999,
|
|
354
|
+
padding: "4px 10px",
|
|
355
|
+
fontSize: 13
|
|
356
|
+
},
|
|
357
|
+
pull: {
|
|
358
|
+
display: "flex",
|
|
359
|
+
alignItems: "center",
|
|
360
|
+
justifyContent: "center",
|
|
361
|
+
color: "#9ca3af",
|
|
362
|
+
fontSize: 13,
|
|
363
|
+
transition: "height 50ms linear"
|
|
364
|
+
},
|
|
365
|
+
viewerShell: { position: "relative", flex: 1 },
|
|
366
|
+
iframe: {
|
|
367
|
+
width: "100%",
|
|
368
|
+
height: "100%",
|
|
369
|
+
border: "none",
|
|
370
|
+
background: "#000"
|
|
371
|
+
},
|
|
372
|
+
inputSurface: {
|
|
373
|
+
position: "absolute",
|
|
374
|
+
inset: 0,
|
|
375
|
+
touchAction: "none",
|
|
376
|
+
background: "transparent"
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
export {
|
|
380
|
+
RemoteSession
|
|
381
|
+
};
|
|
382
|
+
//# sourceMappingURL=RemoteSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/companion/components/RemoteSession.tsx"],"sourcesContent":["import React, {\n type PointerEvent as ReactPointerEvent,\n type TouchEvent as ReactTouchEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport {\n logger,\n type PairingPayload,\n SessionClient,\n type TouchSample,\n touchToInput,\n} from \"../services/index.js\";\n\ninterface RemoteSessionProps {\n payload: PairingPayload;\n onExit(): void;\n}\n\ntype ConnState = \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nconst PULL_TO_REFRESH_THRESHOLD_PX = 80;\nconst PRIVATE_IPV6_PREFIXES = [\"fe80:\", \"fec0:\", \"fc\", \"fd\"] as const;\n\nfunction parseIpv4Address(address: string): number[] | null {\n const parts = address.split(\".\");\n if (parts.length !== 4) {\n return null;\n }\n const numbers = parts.map((part) => Number.parseInt(part, 10));\n if (\n numbers.some((value) => Number.isNaN(value) || value < 0 || value > 255)\n ) {\n return null;\n }\n return numbers;\n}\n\nfunction parseMappedIpv6Address(mappedAddress: string): number[] | null {\n if (mappedAddress.includes(\".\")) {\n return parseIpv4Address(mappedAddress);\n }\n const parts = mappedAddress.split(\":\").filter(Boolean);\n if (parts.length === 1) {\n const value = Number.parseInt(parts[0], 16);\n if (Number.isNaN(value) || value < 0 || value > 0xffff_ffff) {\n return null;\n }\n return [\n (value >>> 24) & 0xff,\n (value >>> 16) & 0xff,\n (value >>> 8) & 0xff,\n value & 0xff,\n ];\n }\n if (parts.length !== 2) {\n return null;\n }\n const high = Number.parseInt(parts[0], 16);\n const low = Number.parseInt(parts[1], 16);\n if (\n Number.isNaN(high) ||\n Number.isNaN(low) ||\n high < 0 ||\n low < 0 ||\n high > 0xffff ||\n low > 0xffff\n ) {\n return null;\n }\n const value = (high << 16) + low;\n return [\n (value >>> 24) & 0xff,\n (value >>> 16) & 0xff,\n (value >>> 8) & 0xff,\n value & 0xff,\n ];\n}\n\nfunction isPrivateIpv4Address(parts: number[]): boolean {\n const [octet1, octet2] = parts;\n return (\n octet1 === 0 ||\n octet1 === 10 ||\n octet1 === 127 ||\n (octet1 === 169 && octet2 === 254) ||\n (octet1 === 172 && octet2 >= 16 && octet2 <= 31) ||\n (octet1 === 192 && octet2 === 168) ||\n (octet1 === 100 && octet2 >= 64 && octet2 <= 127)\n );\n}\n\nfunction isPrivateCompanionHost(host: string): boolean {\n let normalized = host.trim().toLowerCase();\n if (normalized.startsWith(\"[\") && normalized.endsWith(\"]\")) {\n normalized = normalized.slice(1, -1);\n }\n if (normalized.startsWith(\"::ffff:\")) {\n const mapped = parseMappedIpv6Address(normalized.slice(\"::ffff:\".length));\n return mapped !== null && isPrivateIpv4Address(mapped);\n }\n if (normalized.includes(\":\")) {\n return (\n normalized === \"::\" ||\n normalized === \"::1\" ||\n PRIVATE_IPV6_PREFIXES.some((prefix) => normalized.startsWith(prefix))\n );\n }\n const ipv4 = parseIpv4Address(normalized);\n return ipv4 !== null && isPrivateIpv4Address(ipv4);\n}\n\n/**\n * Reject obviously unsafe companion ingress URLs before they are used as an\n * iframe `src` or WebSocket endpoint (phishing / token exfiltration).\n */\nfunction assertSafeCompanionIngressUrl(ingressUrl: string): URL {\n let parsed: URL;\n try {\n parsed = new URL(ingressUrl);\n } catch {\n throw new Error(\"Companion ingress URL is not a valid absolute URL\");\n }\n const protocol = parsed.protocol.toLowerCase();\n if (protocol !== \"wss:\" && protocol !== \"ws:\") {\n throw new Error(\"Companion ingress must use wss: or ws: (WebSocket) URL\");\n }\n if (parsed.hostname.length === 0) {\n throw new Error(\"Companion ingress URL is missing a host\");\n }\n if (parsed.username !== \"\" || parsed.password !== \"\") {\n throw new Error(\"Companion ingress URL must not embed credentials\");\n }\n const host = parsed.hostname.toLowerCase();\n if (host === \"169.254.169.254\" || host === \"metadata.google.internal\") {\n throw new Error(\"Companion ingress host is not allowed\");\n }\n if (protocol === \"ws:\") {\n const allowPlaintextWs =\n host === \"localhost\" ||\n host === \"127.0.0.1\" ||\n host === \"::1\" ||\n isPrivateCompanionHost(host);\n if (!allowPlaintextWs) {\n throw new Error(\n \"ws:// is only allowed on localhost or private networks; use wss:// for this host\",\n );\n }\n }\n return parsed;\n}\n\n/**\n * Builds the noVNC viewer URL from a validated pairing ingress URL.\n * The ingress hosts noVNC at `/vnc` and the input WS at `/input`.\n * Uses the URL API so `/vnc` is joined on pathname, not appended after `?query`\n * (e.g. `wss://host/path/input?k=v` would otherwise yield `...?k=v/vnc`).\n */\nfunction buildViewerUrl(ingressUrl: URL, payload: PairingPayload): string {\n const viewerUrl = new URL(ingressUrl.toString());\n viewerUrl.protocol = ingressUrl.protocol === \"wss:\" ? \"https:\" : \"http:\";\n viewerUrl.pathname = ingressUrl.pathname.replace(/\\/input\\/?$/, \"\") || \"/\";\n viewerUrl.pathname = `${viewerUrl.pathname.replace(/\\/$/, \"\")}/vnc`;\n viewerUrl.searchParams.set(\"token\", payload.sessionToken);\n viewerUrl.searchParams.set(\"agent\", payload.agentId);\n viewerUrl.hash = \"\";\n return viewerUrl.toString();\n}\n\nfunction buildInputUrl(ingressUrl: URL): string {\n const inputUrl = new URL(ingressUrl.toString());\n if (!/\\/input\\/?$/.test(inputUrl.pathname)) {\n inputUrl.pathname = `${inputUrl.pathname.replace(/\\/$/, \"\")}/input`;\n }\n inputUrl.hash = \"\";\n return inputUrl.toString();\n}\n\nexport function RemoteSession({\n payload,\n onExit,\n}: RemoteSessionProps): React.JSX.Element {\n const [connState, setConnState] = useState<ConnState>(\"connecting\");\n const [reconnectNonce, setReconnectNonce] = useState(0);\n const [pullPx, setPullPx] = useState(0);\n const pullPxRef = useRef(0);\n const clientRef = useRef<SessionClient | null>(null);\n const surfaceRef = useRef<HTMLDivElement | null>(null);\n const pointersRef = useRef<Map<number, TouchSample[]>>(new Map());\n const gestureStartRef = useRef<number | null>(null);\n // Scratch accumulator for multi-finger gestures. Not useState to avoid\n // re-renders on every pointer event.\n const completedPointersRef = useRef<TouchSample[][]>([]);\n\n const sessionEndpoints = useMemo(() => {\n try {\n const ingressUrl = assertSafeCompanionIngressUrl(payload.ingressUrl);\n return {\n ok: true as const,\n viewerUrl: buildViewerUrl(ingressUrl, payload),\n inputUrl: buildInputUrl(ingressUrl),\n };\n } catch (cause: unknown) {\n const message = cause instanceof Error ? cause.message : String(cause);\n logger.error(\"[RemoteSession] rejected ingress URL\", { message });\n return { ok: false as const, error: message };\n }\n }, [payload]);\n\n // Connect/reconnect whenever the nonce changes.\n useEffect(() => {\n if (!sessionEndpoints.ok) {\n setConnState(\"error\");\n return;\n }\n const client = new SessionClient();\n clientRef.current = client;\n\n const offState = client.on(\"state\", (state) => {\n if (state === \"connecting\") setConnState(\"connecting\");\n else if (state === \"open\") setConnState(\"open\");\n else if (state === \"closed\") setConnState(\"closed\");\n });\n const offError = client.on(\"error\", () => {\n setConnState(\"error\");\n });\n\n logger.info(\"[RemoteSession] connecting\", {\n agentId: payload.agentId,\n attempt: reconnectNonce,\n });\n client.connect(sessionEndpoints.inputUrl, payload.sessionToken);\n\n return () => {\n offState();\n offError();\n client.close();\n clientRef.current = null;\n };\n }, [payload.agentId, payload.sessionToken, reconnectNonce, sessionEndpoints]);\n\n const reconnect = useCallback(() => {\n logger.info(\"[RemoteSession] reconnect requested\", {});\n setReconnectNonce((n) => n + 1);\n }, []);\n\n const onPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n if (event.pointerType !== \"touch\") return;\n const rect = surfaceRef.current?.getBoundingClientRect();\n if (rect === undefined) return;\n const sample: TouchSample = {\n x: event.clientX - rect.left,\n y: event.clientY - rect.top,\n t: event.timeStamp,\n pointerId: event.pointerId,\n };\n pointersRef.current.set(event.pointerId, [sample]);\n if (gestureStartRef.current === null) {\n gestureStartRef.current = event.timeStamp;\n }\n (event.target as Element).setPointerCapture?.(event.pointerId);\n },\n [],\n );\n\n const onPointerMove = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n if (event.pointerType !== \"touch\") return;\n const samples = pointersRef.current.get(event.pointerId);\n if (samples === undefined) return;\n const rect = surfaceRef.current?.getBoundingClientRect();\n if (rect === undefined) return;\n samples.push({\n x: event.clientX - rect.left,\n y: event.clientY - rect.top,\n t: event.timeStamp,\n pointerId: event.pointerId,\n });\n // Pull-to-refresh indicator: single active pointer, near the top,\n // moving downward.\n if (pointersRef.current.size === 1 && samples.length > 1) {\n const first = samples[0];\n const last = samples[samples.length - 1];\n if (first.y < 40 && last.y > first.y) {\n const next = Math.min(\n last.y - first.y,\n PULL_TO_REFRESH_THRESHOLD_PX * 2,\n );\n pullPxRef.current = next;\n setPullPx(next);\n }\n }\n },\n [],\n );\n\n const onPointerUp = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n if (event.pointerType !== \"touch\") return;\n const samples = pointersRef.current.get(event.pointerId);\n if (samples === undefined) return;\n\n // Wait until all fingers are lifted before translating.\n pointersRef.current.delete(event.pointerId);\n if (pointersRef.current.size > 0) {\n // Save the completed pointer on a scratch map keyed back onto itself.\n // We re-add a zero-length marker so the final gesture can still see\n // the pointer. Simpler: accumulate into gestureEndedPointers below.\n completedPointersRef.current.push(samples);\n return;\n }\n\n completedPointersRef.current.push(samples);\n const pointers = completedPointersRef.current.slice();\n completedPointersRef.current = [];\n gestureStartRef.current = null;\n\n // Pull-to-refresh: if user pulled far enough, reconnect rather than\n // emit a drag.\n const pull = pullPxRef.current;\n if (pull >= PULL_TO_REFRESH_THRESHOLD_PX) {\n pullPxRef.current = 0;\n setPullPx(0);\n reconnect();\n return;\n }\n pullPxRef.current = 0;\n setPullPx(0);\n\n const events = touchToInput({ pointers, ended: true });\n const client = clientRef.current;\n if (client === null) return;\n for (const ev of events) client.sendInput(ev);\n },\n [reconnect],\n );\n\n // Block default iOS touch behaviours on the input surface.\n const onTouchStart = useCallback((event: ReactTouchEvent<HTMLDivElement>) => {\n event.preventDefault();\n }, []);\n\n return (\n <main style={styles.root}>\n <header style={styles.header}>\n <button type=\"button\" onClick={onExit} style={styles.back}>\n Exit\n </button>\n <span style={styles.status}>\n {!sessionEndpoints.ok\n ? sessionEndpoints.error\n : statusLabel(connState)}\n </span>\n <button\n type=\"button\"\n onClick={reconnect}\n disabled={!sessionEndpoints.ok}\n style={styles.reconnect}\n >\n Reconnect\n </button>\n </header>\n\n {pullPx > 0 ? (\n <div\n style={{\n ...styles.pull,\n height: pullPx,\n opacity: Math.min(pullPx / PULL_TO_REFRESH_THRESHOLD_PX, 1),\n }}\n >\n {pullPx >= PULL_TO_REFRESH_THRESHOLD_PX\n ? \"Release to reconnect\"\n : \"Pull to reconnect\"}\n </div>\n ) : null}\n\n <div style={styles.viewerShell}>\n <iframe\n title=\"Remote desktop\"\n src={sessionEndpoints.ok ? sessionEndpoints.viewerUrl : \"about:blank\"}\n style={styles.iframe}\n allow=\"clipboard-read; clipboard-write\"\n sandbox=\"allow-scripts allow-same-origin allow-forms\"\n />\n <div\n ref={surfaceRef}\n style={{\n ...styles.inputSurface,\n pointerEvents: sessionEndpoints.ok ? \"auto\" : \"none\",\n }}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerUp}\n onTouchStart={onTouchStart}\n />\n </div>\n </main>\n );\n}\n\nfunction statusLabel(state: ConnState): string {\n if (state === \"connecting\") return \"Connecting...\";\n if (state === \"open\") return \"Connected\";\n if (state === \"error\") return \"Error\";\n return \"Disconnected\";\n}\n\nconst styles: Record<string, React.CSSProperties> = {\n root: {\n display: \"flex\",\n flexDirection: \"column\",\n height: \"100%\",\n background: \"#000\",\n },\n header: {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n padding: \"12px 16px\",\n background: \"#0a0a0a\",\n borderBottom: \"1px solid #1f2937\",\n },\n back: {\n background: \"transparent\",\n border: \"none\",\n color: \"#93c5fd\",\n fontSize: 16,\n },\n status: { fontSize: 14, opacity: 0.8 },\n reconnect: {\n background: \"transparent\",\n border: \"1px solid #374151\",\n color: \"#e5e7eb\",\n borderRadius: 999,\n padding: \"4px 10px\",\n fontSize: 13,\n },\n pull: {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n color: \"#9ca3af\",\n fontSize: 13,\n transition: \"height 50ms linear\",\n },\n viewerShell: { position: \"relative\", flex: 1 },\n iframe: {\n width: \"100%\",\n height: \"100%\",\n border: \"none\",\n background: \"#000\",\n },\n inputSurface: {\n position: \"absolute\",\n inset: 0,\n touchAction: \"none\",\n background: \"transparent\",\n },\n};\n"],"mappings":"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","names":["value"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/companion/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Chat } from "./Chat.js";
|
|
2
|
+
import { Pairing } from "./Pairing.js";
|
|
3
|
+
import { PhoneCompanionApp } from "./PhoneCompanionApp.js";
|
|
4
|
+
import { RemoteSession } from "./RemoteSession.js";
|
|
5
|
+
export {
|
|
6
|
+
Chat,
|
|
7
|
+
Pairing,
|
|
8
|
+
PhoneCompanionApp,
|
|
9
|
+
RemoteSession
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/companion/components/index.ts"],"sourcesContent":["export { Chat } from \"./Chat.js\";\nexport { Pairing } from \"./Pairing.js\";\nexport { PhoneCompanionApp } from \"./PhoneCompanionApp.js\";\nexport { RemoteSession } from \"./RemoteSession.js\";\n"],"mappings":"AAAA,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phone Companion surface — three-view Capacitor app that pairs with a desktop
|
|
3
|
+
* Eliza agent (QR handshake), mirrors chat, and serves as the remote-session
|
|
4
|
+
* viewer for the paired Mac.
|
|
5
|
+
*
|
|
6
|
+
* Mounted at the root of the app shell when the host bundle runs in
|
|
7
|
+
* companion mode (e.g. the iOS Capacitor build with `?mode=companion`).
|
|
8
|
+
*/
|
|
9
|
+
export { Chat } from "./components/Chat";
|
|
10
|
+
export { Pairing } from "./components/Pairing";
|
|
11
|
+
export { PhoneCompanionApp } from "./components/PhoneCompanionApp";
|
|
12
|
+
export { RemoteSession } from "./components/RemoteSession";
|
|
13
|
+
export * from "./services";
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/companion/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Chat } from "./components/Chat.js";
|
|
2
|
+
import { Pairing } from "./components/Pairing.js";
|
|
3
|
+
import { PhoneCompanionApp } from "./components/PhoneCompanionApp.js";
|
|
4
|
+
import { RemoteSession } from "./components/RemoteSession.js";
|
|
5
|
+
export * from "./services/index.js";
|
|
6
|
+
export {
|
|
7
|
+
Chat,
|
|
8
|
+
Pairing,
|
|
9
|
+
PhoneCompanionApp,
|
|
10
|
+
RemoteSession
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/companion/index.ts"],"sourcesContent":["/**\n * Phone Companion surface — three-view Capacitor app that pairs with a desktop\n * Eliza agent (QR handshake), mirrors chat, and serves as the remote-session\n * viewer for the paired Mac.\n *\n * Mounted at the root of the app shell when the host bundle runs in\n * companion mode (e.g. the iOS Capacitor build with `?mode=companion`).\n */\n\nexport { Chat } from \"./components/Chat.js\";\nexport { Pairing } from \"./components/Pairing.js\";\nexport { PhoneCompanionApp } from \"./components/PhoneCompanionApp.js\";\nexport { RemoteSession } from \"./components/RemoteSession.js\";\nexport * from \"./services/index.js\";\n"],"mappings":"AASA,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAC9B,cAAc;","names":[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { WebPlugin } from "@capacitor/core";
|
|
2
|
+
/**
|
|
3
|
+
* Eliza Intent Plugin — TypeScript facade for the native iOS bridge.
|
|
4
|
+
*
|
|
5
|
+
* On a real device, method calls are routed to `ElizaIntentPlugin.swift`
|
|
6
|
+
* which talks to `UNUserNotificationCenter`, the device-bus subscriber,
|
|
7
|
+
* and `UserDefaults` for pairing persistence. On web (Vite dev / vitest) the fallback below is
|
|
8
|
+
* used — it does not simulate success. It reports `paired: false`, logs
|
|
9
|
+
* each invocation, and rejects native-only calls so dev builds cannot
|
|
10
|
+
* appear to "work" without iOS.
|
|
11
|
+
*/
|
|
12
|
+
export interface ScheduleAlarmOptions {
|
|
13
|
+
timeIso: string;
|
|
14
|
+
title: string;
|
|
15
|
+
body: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ScheduleAlarmResult {
|
|
18
|
+
scheduledId: string;
|
|
19
|
+
timeIso: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ReceiveIntentPayload {
|
|
22
|
+
kind: "alarm" | "reminder" | "block" | "chat";
|
|
23
|
+
payload: Record<string, unknown>;
|
|
24
|
+
issuedAtIso: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ReceiveIntentResult {
|
|
27
|
+
accepted: boolean;
|
|
28
|
+
reason: string;
|
|
29
|
+
}
|
|
30
|
+
export interface PairingStatus {
|
|
31
|
+
paired: boolean;
|
|
32
|
+
agentUrl: string | null;
|
|
33
|
+
deviceId: string | null;
|
|
34
|
+
}
|
|
35
|
+
export interface SetPairingStatusOptions {
|
|
36
|
+
/** Paired agent id from the QR / push payload (stored under `pairingDeviceIdKey` on iOS). */
|
|
37
|
+
deviceId: string;
|
|
38
|
+
/** Session ingress URL (stored under `pairingAgentUrlKey` on iOS). */
|
|
39
|
+
agentUrl: string;
|
|
40
|
+
}
|
|
41
|
+
export interface ElizaIntentPlugin {
|
|
42
|
+
scheduleAlarm(options: ScheduleAlarmOptions): Promise<ScheduleAlarmResult>;
|
|
43
|
+
receiveIntent(intent: ReceiveIntentPayload): Promise<ReceiveIntentResult>;
|
|
44
|
+
getPairingStatus(): Promise<PairingStatus>;
|
|
45
|
+
setPairingStatus(options: SetPairingStatusOptions): Promise<{
|
|
46
|
+
ok: boolean;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Web fallback. Explicitly absent: does not schedule anything, does not
|
|
51
|
+
* pretend to be paired. This lets `bun run dev` boot without a simulator
|
|
52
|
+
* while keeping developers honest about what works.
|
|
53
|
+
*/
|
|
54
|
+
export declare class ElizaIntentWeb extends WebPlugin implements ElizaIntentPlugin {
|
|
55
|
+
scheduleAlarm(options: ScheduleAlarmOptions): Promise<ScheduleAlarmResult>;
|
|
56
|
+
receiveIntent(intent: ReceiveIntentPayload): Promise<ReceiveIntentResult>;
|
|
57
|
+
getPairingStatus(): Promise<PairingStatus>;
|
|
58
|
+
setPairingStatus(options: SetPairingStatusOptions): Promise<{
|
|
59
|
+
ok: boolean;
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
export declare const ElizaIntent: ElizaIntentPlugin;
|
|
63
|
+
//# sourceMappingURL=eliza-intent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eliza-intent.d.ts","sourceRoot":"","sources":["../../../src/companion/services/eliza-intent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5D;;;;;;;;;GASG;AAEH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;IAC9C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,6FAA6F;IAC7F,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC3E,aAAa,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC1E,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3C,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC9E;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,SAAU,YAAW,iBAAiB;IAClE,aAAa,CACjB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC;IASzB,aAAa,CACjB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,mBAAmB,CAAC;IAWzB,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC;IAS1C,gBAAgB,CACpB,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;CAa5B;AAED,eAAO,MAAM,WAAW,mBAGtB,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { registerPlugin, WebPlugin } from "@capacitor/core";
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
class ElizaIntentWeb extends WebPlugin {
|
|
4
|
+
async scheduleAlarm(options) {
|
|
5
|
+
logger.warn("[ElizaIntentWeb] scheduleAlarm not supported on web", {
|
|
6
|
+
options
|
|
7
|
+
});
|
|
8
|
+
throw this.unavailable(
|
|
9
|
+
"ElizaIntent.scheduleAlarm requires iOS native runtime (UNUserNotificationCenter)."
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
async receiveIntent(intent) {
|
|
13
|
+
logger.info("[ElizaIntentWeb] receiveIntent observed (web fallback)", {
|
|
14
|
+
kind: intent.kind,
|
|
15
|
+
issuedAtIso: intent.issuedAtIso
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
accepted: false,
|
|
19
|
+
reason: "web-fallback: no native intent bus available"
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async getPairingStatus() {
|
|
23
|
+
logger.debug("[ElizaIntentWeb] getPairingStatus", {});
|
|
24
|
+
return {
|
|
25
|
+
paired: false,
|
|
26
|
+
agentUrl: null,
|
|
27
|
+
deviceId: null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async setPairingStatus(options) {
|
|
31
|
+
logger.debug("[ElizaIntentWeb] setPairingStatus unavailable on web", {
|
|
32
|
+
deviceIdLength: options.deviceId.length,
|
|
33
|
+
agentUrlHost: (() => {
|
|
34
|
+
try {
|
|
35
|
+
return new URL(options.agentUrl).host;
|
|
36
|
+
} catch {
|
|
37
|
+
return "invalid-url";
|
|
38
|
+
}
|
|
39
|
+
})()
|
|
40
|
+
});
|
|
41
|
+
return { ok: true };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const ElizaIntent = registerPlugin("ElizaIntent", {
|
|
45
|
+
web: () => new ElizaIntentWeb(),
|
|
46
|
+
android: async () => new ElizaIntentWeb()
|
|
47
|
+
});
|
|
48
|
+
export {
|
|
49
|
+
ElizaIntent,
|
|
50
|
+
ElizaIntentWeb
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=eliza-intent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/companion/services/eliza-intent.ts"],"sourcesContent":["import { registerPlugin, WebPlugin } from \"@capacitor/core\";\nimport { logger } from \"./logger.js\";\n\n/**\n * Eliza Intent Plugin — TypeScript facade for the native iOS bridge.\n *\n * On a real device, method calls are routed to `ElizaIntentPlugin.swift`\n * which talks to `UNUserNotificationCenter`, the device-bus subscriber,\n * and `UserDefaults` for pairing persistence. On web (Vite dev / vitest) the fallback below is\n * used — it does not simulate success. It reports `paired: false`, logs\n * each invocation, and rejects native-only calls so dev builds cannot\n * appear to \"work\" without iOS.\n */\n\nexport interface ScheduleAlarmOptions {\n timeIso: string;\n title: string;\n body: string;\n}\n\nexport interface ScheduleAlarmResult {\n scheduledId: string;\n timeIso: string;\n}\n\nexport interface ReceiveIntentPayload {\n kind: \"alarm\" | \"reminder\" | \"block\" | \"chat\";\n payload: Record<string, unknown>;\n issuedAtIso: string;\n}\n\nexport interface ReceiveIntentResult {\n accepted: boolean;\n reason: string;\n}\n\nexport interface PairingStatus {\n paired: boolean;\n agentUrl: string | null;\n deviceId: string | null;\n}\n\nexport interface SetPairingStatusOptions {\n /** Paired agent id from the QR / push payload (stored under `pairingDeviceIdKey` on iOS). */\n deviceId: string;\n /** Session ingress URL (stored under `pairingAgentUrlKey` on iOS). */\n agentUrl: string;\n}\n\nexport interface ElizaIntentPlugin {\n scheduleAlarm(options: ScheduleAlarmOptions): Promise<ScheduleAlarmResult>;\n receiveIntent(intent: ReceiveIntentPayload): Promise<ReceiveIntentResult>;\n getPairingStatus(): Promise<PairingStatus>;\n setPairingStatus(options: SetPairingStatusOptions): Promise<{ ok: boolean }>;\n}\n\n/**\n * Web fallback. Explicitly absent: does not schedule anything, does not\n * pretend to be paired. This lets `bun run dev` boot without a simulator\n * while keeping developers honest about what works.\n */\nexport class ElizaIntentWeb extends WebPlugin implements ElizaIntentPlugin {\n async scheduleAlarm(\n options: ScheduleAlarmOptions,\n ): Promise<ScheduleAlarmResult> {\n logger.warn(\"[ElizaIntentWeb] scheduleAlarm not supported on web\", {\n options,\n });\n throw this.unavailable(\n \"ElizaIntent.scheduleAlarm requires iOS native runtime (UNUserNotificationCenter).\",\n );\n }\n\n async receiveIntent(\n intent: ReceiveIntentPayload,\n ): Promise<ReceiveIntentResult> {\n logger.info(\"[ElizaIntentWeb] receiveIntent observed (web fallback)\", {\n kind: intent.kind,\n issuedAtIso: intent.issuedAtIso,\n });\n return {\n accepted: false,\n reason: \"web-fallback: no native intent bus available\",\n };\n }\n\n async getPairingStatus(): Promise<PairingStatus> {\n logger.debug(\"[ElizaIntentWeb] getPairingStatus\", {});\n return {\n paired: false,\n agentUrl: null,\n deviceId: null,\n };\n }\n\n async setPairingStatus(\n options: SetPairingStatusOptions,\n ): Promise<{ ok: boolean }> {\n logger.debug(\"[ElizaIntentWeb] setPairingStatus unavailable on web\", {\n deviceIdLength: options.deviceId.length,\n agentUrlHost: (() => {\n try {\n return new URL(options.agentUrl).host;\n } catch {\n return \"invalid-url\";\n }\n })(),\n });\n return { ok: true };\n }\n}\n\nexport const ElizaIntent = registerPlugin<ElizaIntentPlugin>(\"ElizaIntent\", {\n web: () => new ElizaIntentWeb(),\n android: async () => new ElizaIntentWeb(),\n});\n"],"mappings":"AAAA,SAAS,gBAAgB,iBAAiB;AAC1C,SAAS,cAAc;AA4DhB,MAAM,uBAAuB,UAAuC;AAAA,EACzE,MAAM,cACJ,SAC8B;AAC9B,WAAO,KAAK,uDAAuD;AAAA,MACjE;AAAA,IACF,CAAC;AACD,UAAM,KAAK;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,cACJ,QAC8B;AAC9B,WAAO,KAAK,0DAA0D;AAAA,MACpE,MAAM,OAAO;AAAA,MACb,aAAa,OAAO;AAAA,IACtB,CAAC;AACD,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,mBAA2C;AAC/C,WAAO,MAAM,qCAAqC,CAAC,CAAC;AACpD,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,MAAM,iBACJ,SAC0B;AAC1B,WAAO,MAAM,wDAAwD;AAAA,MACnE,gBAAgB,QAAQ,SAAS;AAAA,MACjC,eAAe,MAAM;AACnB,YAAI;AACF,iBAAO,IAAI,IAAI,QAAQ,QAAQ,EAAE;AAAA,QACnC,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF,GAAG;AAAA,IACL,CAAC;AACD,WAAO,EAAE,IAAI,KAAK;AAAA,EACpB;AACF;AAEO,MAAM,cAAc,eAAkC,eAAe;AAAA,EAC1E,KAAK,MAAM,IAAI,eAAe;AAAA,EAC9B,SAAS,YAAY,IAAI,eAAe;AAC1C,CAAC;","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime environment for the phone-companion views.
|
|
3
|
+
*
|
|
4
|
+
* Values come from Vite's `import.meta.env`. Missing or empty configuration is
|
|
5
|
+
* represented explicitly: `agentUrl()` returns `null` and `apnsEnabled()`
|
|
6
|
+
* returns `false` rather than throwing, so callers can branch without try/catch.
|
|
7
|
+
*/
|
|
8
|
+
export declare function agentUrl(): string | null;
|
|
9
|
+
export declare function apnsEnabled(): boolean;
|
|
10
|
+
export declare function isDev(): boolean;
|
|
11
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../src/companion/services/env.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAcH,wBAAgB,QAAQ,IAAI,MAAM,GAAG,IAAI,CAGxC;AAED,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED,wBAAgB,KAAK,IAAI,OAAO,CAE/B"}
|