ugly-app 0.1.682 → 0.1.683

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.
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.1.682";
1
+ export declare const CLI_VERSION = "0.1.683";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.682";
2
+ export const CLI_VERSION = "0.1.683";
3
3
  //# sourceMappingURL=version.js.map
@@ -17,7 +17,7 @@ export type { TerminalFacade } from './facades/terminal.js';
17
17
  export { MCP_CATALOG } from './mcp-catalog.js';
18
18
  export { createProxyClient, nativeSigner } from './proxyClient.js';
19
19
  export type { ProxyClient, ProxyClientOpts, ProxySocket, HostInfo, RTCFactory, } from './proxyClient.js';
20
- export { isDesktopChannel, withProxy, dispatchNativeEvent, DESKTOP_NAMESPACES } from './proxy.js';
20
+ export { isDesktopChannel, withProxy, enableUglyProxy, dispatchNativeEvent, DESKTOP_NAMESPACES } from './proxy.js';
21
21
  export type { WithProxyOpts } from './proxy.js';
22
22
  export { fpTag, verifyFpTag, parseFingerprint, mskSigner } from './proxyCrypto.js';
23
23
  export type { ProxyRole, ProxySigner } from './proxyCrypto.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/native/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,OAAO,EACP,WAAW,EACX,SAAS,EACT,QAAQ,EACR,MAAM,EACN,SAAS,EACT,cAAc,EACd,aAAa,EACb,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EACd,aAAa,EACb,aAAa,EACb,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,eAAe,EACf,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/E,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtF,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC/D,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACnE,YAAY,EACV,WAAW,EACX,eAAe,EACf,WAAW,EACX,QAAQ,EACR,UAAU,GACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAClG,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACnF,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/native/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,OAAO,EACP,WAAW,EACX,SAAS,EACT,QAAQ,EACR,MAAM,EACN,SAAS,EACT,cAAc,EACd,aAAa,EACb,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EACd,aAAa,EACb,aAAa,EACb,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,eAAe,EACf,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/E,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtF,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC/D,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACnE,YAAY,EACV,WAAW,EACX,eAAe,EACf,WAAW,EACX,QAAQ,EACR,UAAU,GACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,eAAe,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACnH,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACnF,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
@@ -6,6 +6,6 @@ export { native, permissions, nativePlatform, isNativeAvailable } from './wrappe
6
6
  export { MCP_CATALOG } from './mcp-catalog.js';
7
7
  // ── Ugly Proxy (remote UglyNative over WebRTC) ──
8
8
  export { createProxyClient, nativeSigner } from './proxyClient.js';
9
- export { isDesktopChannel, withProxy, dispatchNativeEvent, DESKTOP_NAMESPACES } from './proxy.js';
9
+ export { isDesktopChannel, withProxy, enableUglyProxy, dispatchNativeEvent, DESKTOP_NAMESPACES } from './proxy.js';
10
10
  export { fpTag, verifyFpTag, parseFingerprint, mskSigner } from './proxyCrypto.js';
11
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/native/index.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE/E,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAStF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,mDAAmD;AACnD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAQnE,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAElG,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/native/index.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE/E,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAStF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,mDAAmD;AACnD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAQnE,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,eAAe,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEnH,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
@@ -20,6 +20,17 @@ export interface WithProxyOpts {
20
20
  * to a `dispatchNativeEvent` (below) — then `base.subscribe` delivers them unchanged.
21
21
  */
22
22
  export declare function withProxy(base: UglyNative, opts: WithProxyOpts): UglyNative;
23
+ /**
24
+ * Full mobile/web client bootstrap: returns an UglyNative whose desktop-capability calls
25
+ * lazily connect to a host and proxy. On the first such call (no host yet) it drives the
26
+ * native host-picker (proxy.pickHost) + status overlay (proxy.setStatus); afterwards it
27
+ * reconnects to the same host silently. Device-capability calls stay local. Wire the
28
+ * client's `onEvent` to dispatchNativeEvent so host events reach `subscribe`.
29
+ */
30
+ export declare function enableUglyProxy(opts: {
31
+ native: UglyNative;
32
+ client: ProxyClient;
33
+ }): UglyNative;
23
34
  /** Dispatch a proxy event onto the same 'uglyNative' CustomEvent bus base.subscribe reads. */
24
35
  export declare function dispatchNativeEvent(win: {
25
36
  dispatchEvent(e: unknown): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/native/proxy.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAYjD,CAAC;AAEH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,WAAW,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3C,+EAA+E;IAC/E,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,GAAG,UAAU,CAc3E;AAED,8FAA8F;AAC9F,wBAAgB,mBAAmB,CACjC,GAAG,EAAE;IAAE,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;CAAE,EAC3C,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,OAAO,GACZ,IAAI,CAON"}
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/native/proxy.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,KAAK,EAAE,WAAW,EAAY,MAAM,kBAAkB,CAAC;AAE9D,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAYjD,CAAC;AAEH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,WAAW,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3C,+EAA+E;IAC/E,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,GAAG,UAAU,CAc3E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;CACrB,GAAG,UAAU,CAyDb;AAED,8FAA8F;AAC9F,wBAAgB,mBAAmB,CACjC,GAAG,EAAE;IAAE,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;CAAE,EAC3C,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,OAAO,GACZ,IAAI,CAON"}
@@ -38,6 +38,79 @@ export function withProxy(base, opts) {
38
38
  subscribe: base.subscribe,
39
39
  };
40
40
  }
41
+ /**
42
+ * Full mobile/web client bootstrap: returns an UglyNative whose desktop-capability calls
43
+ * lazily connect to a host and proxy. On the first such call (no host yet) it drives the
44
+ * native host-picker (proxy.pickHost) + status overlay (proxy.setStatus); afterwards it
45
+ * reconnects to the same host silently. Device-capability calls stay local. Wire the
46
+ * client's `onEvent` to dispatchNativeEvent so host events reach `subscribe`.
47
+ */
48
+ export function enableUglyProxy(opts) {
49
+ const { native, client } = opts;
50
+ let connecting = null;
51
+ let lastLabel;
52
+ const setStatus = (state, hostLabel) => {
53
+ void native.invoke('proxy.setStatus', { state, hostLabel }).catch(() => { });
54
+ };
55
+ async function ensureConnected() {
56
+ if (client.isOpen())
57
+ return true;
58
+ if (connecting)
59
+ return connecting;
60
+ connecting = (async () => {
61
+ try {
62
+ const existing = client.hostDeviceId();
63
+ if (existing) {
64
+ // Silent reconnect to the previously chosen host.
65
+ setStatus('reconnecting', lastLabel);
66
+ await client.connect(existing);
67
+ setStatus('connected', lastLabel);
68
+ return true;
69
+ }
70
+ setStatus('connecting');
71
+ const hosts = await client.listHosts();
72
+ const picked = (await native.invoke('proxy.pickHost', {
73
+ hosts: hosts.map((h) => ({ deviceId: h.deviceId, label: h.label, online: true, lastSeen: h.lastSeen })),
74
+ }));
75
+ if (!picked) {
76
+ setStatus('none');
77
+ return false;
78
+ }
79
+ lastLabel = hosts.find((h) => h.deviceId === picked.deviceId)?.label;
80
+ await client.connect(picked.deviceId);
81
+ setStatus('connected', lastLabel);
82
+ return true;
83
+ }
84
+ catch {
85
+ setStatus(client.hostDeviceId() ? 'offline' : 'none');
86
+ return false;
87
+ }
88
+ finally {
89
+ connecting = null;
90
+ }
91
+ })();
92
+ return connecting;
93
+ }
94
+ return {
95
+ platform: native.platform,
96
+ subscribe: native.subscribe,
97
+ invoke: (async (channel, payload) => {
98
+ if (!isDesktopChannel(channel))
99
+ return native.invoke(channel, payload);
100
+ if (!client.isOpen() && !(await ensureConnected()))
101
+ throw new NativeUnavailableError(channel);
102
+ try {
103
+ return await client.invoke(channel, payload);
104
+ }
105
+ catch (e) {
106
+ // Mid-session drop: surface the overlay, reconnect to the same host, retry once.
107
+ if (!client.isOpen() && (await ensureConnected()))
108
+ return client.invoke(channel, payload);
109
+ throw e;
110
+ }
111
+ }),
112
+ };
113
+ }
41
114
  /** Dispatch a proxy event onto the same 'uglyNative' CustomEvent bus base.subscribe reads. */
42
115
  export function dispatchNativeEvent(win, event, data) {
43
116
  const detail = { event, data };
@@ -1 +1 @@
1
- {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/native/proxy.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAGvD,4FAA4F;AAC5F,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC;IAC7D,IAAI;IACJ,SAAS;IACT,KAAK;IACL,OAAO;IACP,SAAS;IACT,UAAU;IACV,SAAS;IACT,KAAK;IACL,SAAS;IACT,QAAQ;IACR,MAAM;CACP,CAAC,CAAC;AAEH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACpF,OAAO,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACpC,CAAC;AAcD;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,IAAgB,EAAE,IAAmB;IAC7D,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACvG,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,CAAC,CAAC,OAAe,EAAE,OAAgB,EAAE,EAAE;YAC7C,IAAI,WAAW,CAAC,OAAO,CAAC;gBAAE,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACtE,IAAI,IAAI,CAAC,wBAAwB,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/D,0EAA0E;gBAC1E,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;YAC7D,CAAC;YACD,OAAO,IAAI,CAAC,MAAM,CAAC,OAAgB,EAAE,OAAgB,CAAC,CAAC;QACzD,CAAC,CAAyB;QAC1B,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC;AACJ,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,mBAAmB,CACjC,GAA2C,EAC3C,KAAa,EACb,IAAa;IAEb,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC/B,MAAM,EAAE,GACN,OAAO,WAAW,KAAK,UAAU;QAC/B,CAAC,CAAC,IAAI,WAAW,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,CAAC;QAC3C,CAAC,CAAE,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAuB,CAAC;IAC3D,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;AACxB,CAAC"}
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/native/proxy.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-app",
3
- "version": "0.1.682",
3
+ "version": "0.1.683",
4
4
  "type": "module",
5
5
  "comment:files": "Allowlist what ships to npm. dist = runtime; src = sourcemap targets (dist/*.js.map reference ../../src/); templates = CLI scaffold. Everything else at repo root (.pgdata local Postgres, coverage, assets/icons sources, test/, test-results/) is excluded by omission. The !negations strip the scaffold's installed deps + cruft (templates/node_modules is 200MB+ and must never ship). package.json/README/LICENSE ship automatically.",
6
6
  "files": [
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.682";
2
+ export const CLI_VERSION = "0.1.683";
@@ -58,7 +58,7 @@ export type {
58
58
  HostInfo,
59
59
  RTCFactory,
60
60
  } from './proxyClient.js';
61
- export { isDesktopChannel, withProxy, dispatchNativeEvent, DESKTOP_NAMESPACES } from './proxy.js';
61
+ export { isDesktopChannel, withProxy, enableUglyProxy, dispatchNativeEvent, DESKTOP_NAMESPACES } from './proxy.js';
62
62
  export type { WithProxyOpts } from './proxy.js';
63
63
  export { fpTag, verifyFpTag, parseFingerprint, mskSigner } from './proxyCrypto.js';
64
64
  export type { ProxyRole, ProxySigner } from './proxyCrypto.js';
@@ -5,7 +5,7 @@
5
5
  // base UglyNative with `withProxy(...)`; the core install.ts is untouched.
6
6
  import type { UglyNative } from './protocol.js';
7
7
  import { NativeUnavailableError } from './protocol.js';
8
- import type { ProxyClient } from './proxyClient.js';
8
+ import type { ProxyClient, HostInfo } from './proxyClient.js';
9
9
 
10
10
  /** Namespaces that require a desktop host (RCE-grade / filesystem / long-running tasks). */
11
11
  export const DESKTOP_NAMESPACES: ReadonlySet<string> = new Set([
@@ -60,6 +60,75 @@ export function withProxy(base: UglyNative, opts: WithProxyOpts): UglyNative {
60
60
  };
61
61
  }
62
62
 
63
+ /**
64
+ * Full mobile/web client bootstrap: returns an UglyNative whose desktop-capability calls
65
+ * lazily connect to a host and proxy. On the first such call (no host yet) it drives the
66
+ * native host-picker (proxy.pickHost) + status overlay (proxy.setStatus); afterwards it
67
+ * reconnects to the same host silently. Device-capability calls stay local. Wire the
68
+ * client's `onEvent` to dispatchNativeEvent so host events reach `subscribe`.
69
+ */
70
+ export function enableUglyProxy(opts: {
71
+ native: UglyNative;
72
+ client: ProxyClient;
73
+ }): UglyNative {
74
+ const { native, client } = opts;
75
+ let connecting: Promise<boolean> | null = null;
76
+ let lastLabel: string | undefined;
77
+
78
+ const setStatus = (state: string, hostLabel?: string): void => {
79
+ void native.invoke('proxy.setStatus' as never, { state, hostLabel } as never).catch(() => {});
80
+ };
81
+
82
+ async function ensureConnected(): Promise<boolean> {
83
+ if (client.isOpen()) return true;
84
+ if (connecting) return connecting;
85
+ connecting = (async () => {
86
+ try {
87
+ const existing = client.hostDeviceId();
88
+ if (existing) {
89
+ // Silent reconnect to the previously chosen host.
90
+ setStatus('reconnecting', lastLabel);
91
+ await client.connect(existing);
92
+ setStatus('connected', lastLabel);
93
+ return true;
94
+ }
95
+ setStatus('connecting');
96
+ const hosts = await client.listHosts();
97
+ const picked = (await native.invoke('proxy.pickHost' as never, {
98
+ hosts: hosts.map((h: HostInfo) => ({ deviceId: h.deviceId, label: h.label, online: true, lastSeen: h.lastSeen })),
99
+ } as never)) as { deviceId: string } | null;
100
+ if (!picked) { setStatus('none'); return false; }
101
+ lastLabel = hosts.find((h) => h.deviceId === picked.deviceId)?.label;
102
+ await client.connect(picked.deviceId);
103
+ setStatus('connected', lastLabel);
104
+ return true;
105
+ } catch {
106
+ setStatus(client.hostDeviceId() ? 'offline' : 'none');
107
+ return false;
108
+ } finally {
109
+ connecting = null;
110
+ }
111
+ })();
112
+ return connecting;
113
+ }
114
+
115
+ return {
116
+ platform: native.platform,
117
+ subscribe: native.subscribe,
118
+ invoke: (async (channel: string, payload: unknown) => {
119
+ if (!isDesktopChannel(channel)) return native.invoke(channel as never, payload as never);
120
+ if (!client.isOpen() && !(await ensureConnected())) throw new NativeUnavailableError(channel);
121
+ try {
122
+ return await client.invoke(channel, payload);
123
+ } catch (e) {
124
+ // Mid-session drop: surface the overlay, reconnect to the same host, retry once.
125
+ if (!client.isOpen() && (await ensureConnected())) return client.invoke(channel, payload);
126
+ throw e;
127
+ }
128
+ }) as UglyNative['invoke'],
129
+ };
130
+ }
131
+
63
132
  /** Dispatch a proxy event onto the same 'uglyNative' CustomEvent bus base.subscribe reads. */
64
133
  export function dispatchNativeEvent(
65
134
  win: { dispatchEvent(e: unknown): boolean },
@@ -0,0 +1,84 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { enableUglyProxy } from './proxy.js';
3
+ import { NativeUnavailableError } from './protocol.js';
4
+ import type { UglyNative } from './protocol.js';
5
+ import type { ProxyClient } from './proxyClient.js';
6
+
7
+ function fakeNative(pick: { deviceId: string } | null): {
8
+ native: UglyNative;
9
+ calls: Array<{ channel: string; payload: unknown }>;
10
+ } {
11
+ const calls: Array<{ channel: string; payload: unknown }> = [];
12
+ const native: UglyNative = {
13
+ platform: 'ios',
14
+ subscribe: vi.fn(() => () => {}) as unknown as UglyNative['subscribe'],
15
+ invoke: (async (channel: string, payload: unknown) => {
16
+ calls.push({ channel, payload });
17
+ if (channel === 'proxy.pickHost') return pick;
18
+ if (channel === 'proxy.setStatus') return undefined;
19
+ return { local: channel }; // device-cap local impl
20
+ }) as unknown as UglyNative['invoke'],
21
+ };
22
+ return { native, calls };
23
+ }
24
+
25
+ function fakeClient(): ProxyClient & { _open: boolean; _host: string | null } {
26
+ const c = {
27
+ _open: false,
28
+ _host: null as string | null,
29
+ listHosts: vi.fn(async () => [{ deviceId: 'h1', label: 'My Mac', isHost: true, lastSeen: 1 }]),
30
+ connect: vi.fn(async (id: string) => { c._open = true; c._host = id; }),
31
+ invoke: vi.fn(async (channel: string, payload: unknown) => ({ proxied: channel, payload })),
32
+ subscribe: vi.fn(async () => ({ snapshot: undefined, unsubscribe: () => {} })),
33
+ isOpen: () => c._open,
34
+ hostDeviceId: () => c._host,
35
+ close: () => { c._open = false; },
36
+ };
37
+ return c as ProxyClient & { _open: boolean; _host: string | null };
38
+ }
39
+
40
+ const statuses = (calls: Array<{ channel: string; payload: unknown }>) =>
41
+ calls.filter((c) => c.channel === 'proxy.setStatus').map((c) => (c.payload as { state: string }).state);
42
+
43
+ describe('enableUglyProxy', () => {
44
+ it('keeps device-cap calls local', async () => {
45
+ const { native } = fakeNative({ deviceId: 'h1' });
46
+ const client = fakeClient();
47
+ const n = enableUglyProxy({ native, client });
48
+ expect(await n.invoke('clipboard.write' as never, { text: 'x' } as never)).toEqual({ local: 'clipboard.write' });
49
+ expect(client.connect).not.toHaveBeenCalled();
50
+ });
51
+
52
+ it('on first desktop-cap call: picks a host, connects, proxies, and reports status', async () => {
53
+ const { native, calls } = fakeNative({ deviceId: 'h1' });
54
+ const client = fakeClient();
55
+ const n = enableUglyProxy({ native, client });
56
+ const r = await n.invoke('fs.readFile' as never, { path: '/p' } as never);
57
+ expect(client.listHosts).toHaveBeenCalled();
58
+ expect(calls.some((c) => c.channel === 'proxy.pickHost')).toBe(true);
59
+ expect(client.connect).toHaveBeenCalledWith('h1');
60
+ expect(r).toEqual({ proxied: 'fs.readFile', payload: { path: '/p' } });
61
+ expect(statuses(calls)).toEqual(['connecting', 'connected']);
62
+ });
63
+
64
+ it('throws + reports none when the user cancels the picker', async () => {
65
+ const { native, calls } = fakeNative(null);
66
+ const client = fakeClient();
67
+ const n = enableUglyProxy({ native, client });
68
+ await expect(n.invoke('process.spawn' as never, {} as never)).rejects.toBeInstanceOf(NativeUnavailableError);
69
+ expect(client.connect).not.toHaveBeenCalled();
70
+ expect(statuses(calls)).toEqual(['connecting', 'none']);
71
+ });
72
+
73
+ it('reconnects silently to the same host on a later call (no re-pick)', async () => {
74
+ const { native, calls } = fakeNative({ deviceId: 'h1' });
75
+ const client = fakeClient();
76
+ const n = enableUglyProxy({ native, client });
77
+ await n.invoke('fs.readFile' as never, {} as never); // connect once
78
+ client._open = false; // simulate drop
79
+ const r = await n.invoke('fs.readFile' as never, { again: true } as never);
80
+ expect(r).toMatchObject({ proxied: 'fs.readFile' });
81
+ expect(calls.filter((c) => c.channel === 'proxy.pickHost')).toHaveLength(1); // only the first time
82
+ expect(statuses(calls)).toEqual(['connecting', 'connected', 'reconnecting', 'connected']);
83
+ });
84
+ });