@livx.cc/appwrap 0.46.1 → 0.46.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livx.cc/appwrap",
3
- "version": "0.46.1",
3
+ "version": "0.46.2",
4
4
  "description": "Wrap any PWA into a native app with native capabilities (appwrap runtime + @livx.cc/native-kit).",
5
5
  "license": "MIT",
6
6
  "author": "Elya Livshitz",
@@ -53,6 +53,7 @@
53
53
  <category android:name="android.intent.category.DEFAULT" />
54
54
  <category android:name="android.intent.category.BROWSABLE" />
55
55
  <data android:scheme="appwrap" />
56
+ <!-- appwrap:oauth-redirect-schemes --><!-- /appwrap:oauth-redirect-schemes -->
56
57
  </intent-filter>
57
58
  </activity>
58
59
  <activity android:name="com.tns.ErrorReportActivity"/>
@@ -16,27 +16,13 @@ const RUST_PORT = Number(process.env.APPWRAP_BRIDGE_RUST_PORT || 9237);
16
16
 
17
17
  const log = (m: string, ...a: unknown[]) => console.log(`[shim] ${m}`, ...a);
18
18
 
19
- // ---------------------------------------------------------------------------
20
- // Tab registry: Tauri window label <-> numeric tabId (chrome.tabs.Tab-ish shape)
21
- // ---------------------------------------------------------------------------
22
- interface TabEntry { tabId: number; label: string; url: string; title: string; registered: boolean; profileId?: string }
23
- const tabs = new Map<string, TabEntry>(); // by label
24
- let tabCounter = 0;
25
- let activeLabel: string | null = null;
26
-
27
- function tabShape(t: TabEntry, active = t.label === activeLabel) {
28
- return { id: t.tabId, url: t.url, title: t.title, active, windowId: 1, status: t.registered ? 'complete' : 'loading', label: t.label, profileId: t.profileId };
29
- }
30
- function byTabId(tabId?: number): TabEntry {
31
- if (tabId != null) {
32
- const t = [...tabs.values()].find(t => t.tabId === Number(tabId));
33
- if (!t) throw new Error(`No such tab: ${tabId}`);
34
- return t;
35
- }
36
- const t = (activeLabel && tabs.get(activeLabel)) || [...tabs.values()].pop();
37
- if (!t) throw new Error('No windows open (use newTab first)');
38
- return t;
39
- }
19
+ // The transport-agnostic browser-automation PRODUCT (tab registry + bod-browser routing +
20
+ // handleCommand) was lifted into the `browser-driver` package inside mcp-desktop-browser (Phase 1b).
21
+ // This file is now a thin TRANSPORT shell: WS client + TCP control channel + page WS, delegating all
22
+ // command handling and page-beacon parsing to the driver below. Consumed as a proper package
23
+ // (`mcp-desktop-browser/browser-driver`, a file: dep in the root package.json → resolves by name,
24
+ // not a fragile cross-repo relative path — the TS analogue of the crate-consumption decision).
25
+ import { createBrowserDriver } from 'mcp-desktop-browser/browser-driver';
40
26
 
41
27
  // ---------------------------------------------------------------------------
42
28
  // Rust control channel (TCP, newline-delimited JSON)
@@ -56,6 +42,9 @@ function rustRequest(op: string, params: Record<string, unknown> = {}, timeoutMs
56
42
  });
57
43
  }
58
44
 
45
+ // The product layer — driven through our transport (rustRequest) + fed page beacons via onPageMsg.
46
+ const driver = createBrowserDriver({ rustRequest, log });
47
+
59
48
  Bun.listen({
60
49
  hostname: '127.0.0.1',
61
50
  port: RUST_PORT,
@@ -75,7 +64,7 @@ Bun.listen({
75
64
  if (!line) continue;
76
65
  try {
77
66
  const msg = JSON.parse(line);
78
- if (msg.type === 'pageMsg') { onPageMsg(String(msg.payload)); continue; }
67
+ if (msg.type === 'pageMsg') { driver.onPageMsg(String(msg.payload)); continue; }
79
68
  const p = rustPending.get(msg.id);
80
69
  if (p) {
81
70
  rustPending.delete(msg.id);
@@ -91,34 +80,6 @@ Bun.listen({
91
80
  },
92
81
  });
93
82
 
94
- // ---------------------------------------------------------------------------
95
- // Page messages (via the shell's WKScriptMessageHandler -> pageMsg lines) and
96
- // command dispatch (via evalJs -> window.__bridgeDispatch)
97
- // ---------------------------------------------------------------------------
98
- const pagePending = new Map<string, Pending>();
99
-
100
- function onPageMsg(payload: string) {
101
- let msg: Record<string, unknown>;
102
- try { msg = JSON.parse(payload); } catch { log('bad pageMsg:', payload.slice(0, 200)); return; }
103
- const label = String(msg.label || '');
104
- if (msg.type === 'register') {
105
- let t = tabs.get(label);
106
- if (!t) { t = { tabId: ++tabCounter, label, url: '', title: '', registered: false }; tabs.set(label, t); }
107
- t.registered = true;
108
- t.url = String(msg.url || t.url);
109
- t.title = String(msg.title || t.title);
110
- log(`page registered: ${label} tabId=${t.tabId} ${t.url.slice(0, 80)}`);
111
- return;
112
- }
113
- const p = pagePending.get(String(msg.id));
114
- if (p) {
115
- pagePending.delete(String(msg.id));
116
- clearTimeout(p.timer);
117
- if (msg.error) p.reject(new Error(String(msg.error)));
118
- else p.resolve(msg.result);
119
- }
120
- }
121
-
122
83
  // Debug HTTP endpoint (curl a raw rust op / read page beacons)
123
84
  Bun.serve({
124
85
  hostname: '127.0.0.1',
@@ -135,152 +96,6 @@ Bun.serve({
135
96
  },
136
97
  });
137
98
 
138
- function pageExec(t: TabEntry, action: Record<string, unknown>, timeoutMs = 30000): Promise<unknown> {
139
- const id = `p${++reqCounter}`;
140
- const dispatch = `window.__bridgeDispatch && window.__bridgeDispatch(${JSON.stringify({ id, ...action })})`;
141
- return new Promise((resolve, reject) => {
142
- const timer = setTimeout(() => { pagePending.delete(id); reject(new Error(`exec ${action.action} timed out on tab ${t.tabId} (page not bridged?)`)); }, timeoutMs);
143
- pagePending.set(id, { resolve, reject, timer });
144
- rustRequest('evalJs', { label: t.label, js: dispatch }).catch(e => {
145
- if (pagePending.delete(id)) { clearTimeout(timer); reject(e as Error); }
146
- });
147
- });
148
- }
149
-
150
- /** Wait until the tab's page socket is (re)connected — used after newTab/navigate. */
151
- async function waitForPage(label: string, timeoutMs = 15000): Promise<TabEntry> {
152
- const start = Date.now();
153
- for (;;) {
154
- const t = tabs.get(label);
155
- if (t?.registered) return t;
156
- if (Date.now() - start > timeoutMs) throw new Error(`page for ${label} did not connect within ${timeoutMs}ms`);
157
- await new Promise(r => setTimeout(r, 100));
158
- }
159
- }
160
-
161
- // ---------------------------------------------------------------------------
162
- // Command handling (the extension-background analogue)
163
- // ---------------------------------------------------------------------------
164
- async function handleCommand(msg: { id: string; cmd: string; params?: Record<string, unknown> }): Promise<unknown> {
165
- const params = (msg.params || {}) as Record<string, unknown> & { tabId?: number; url?: string };
166
- switch (msg.cmd) {
167
- case 'ping': return { pong: true };
168
- case 'getTabs': return [...tabs.values()].map(t => tabShape(t));
169
- case 'getActiveTab': return tabShape(byTabId(undefined));
170
- case 'newTab': {
171
- const url = params.url || 'https://example.com';
172
- // Optional profileId → the shell creates a manual window bound to that profile's persistent,
173
- // isolated WKWebsiteDataStore (see bridge_mac.rs). Absent → a plain tauri window.
174
- const profileId = (params as { profileId?: string }).profileId;
175
- // Unique across shim restarts — the app may still hold windows from a previous shim run.
176
- const label = `bridge-${Date.now().toString(36)}-${++tabCounter}`;
177
- const t: TabEntry = { tabId: tabCounter, label, url: String(url), title: '', registered: false, profileId };
178
- tabs.set(label, t);
179
- activeLabel = label;
180
- await rustRequest('createWindow', { label, url, ...(profileId ? { profileId } : {}) });
181
- await waitForPage(label).catch(e => log(`newTab: ${e.message}`)); // tab exists even if slow to load
182
- return tabShape(t);
183
- }
184
- case 'closeTab': {
185
- const t = byTabId(params.tabId);
186
- await rustRequest('closeWindow', { label: t.label });
187
- tabs.delete(t.label);
188
- if (activeLabel === t.label) activeLabel = [...tabs.keys()].pop() ?? null;
189
- return { success: true };
190
- }
191
- case 'navigate': {
192
- const t = byTabId(params.tabId);
193
- t.registered = false; // old document is going away
194
- t.url = String(params.url);
195
- activeLabel = t.label;
196
- await rustRequest('navigate', { label: t.label, url: params.url });
197
- await waitForPage(t.label).catch(e => log(`navigate: ${e.message}`));
198
- return tabShape(t);
199
- }
200
- case 'screenshot': {
201
- const t = byTabId(params.tabId);
202
- const r = await rustRequest('screenshot', { label: t.label }, 20000) as { png: string };
203
- return { dataUrl: `data:image/png;base64,${r.png}` };
204
- }
205
- case 'exec': {
206
- const { tabId, frameId: _f, ...action } = params as Record<string, unknown> & { tabId?: number };
207
- const t = byTabId(tabId);
208
- const timeout = action.action === 'waitFor' ? Number(action.timeout || 5000) + 5000 : 30000;
209
- return pageExec(t, action, timeout);
210
- }
211
- case 'setInput': {
212
- // Extension does this via chrome.scripting in MAIN world; our bundle already runs in the
213
- // page world, so route through the content eval with a native-setter script.
214
- const t = byTabId(params.tabId);
215
- const script = `(() => { const el = document.querySelector(${JSON.stringify(params.selector)});
216
- if (!el) return { error: 'not found: ' + ${JSON.stringify(params.selector)} };
217
- const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
218
- const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
219
- if (setter) setter.call(el, ${JSON.stringify(params.value)}); else el.value = ${JSON.stringify(params.value)};
220
- el.dispatchEvent(new Event('input', { bubbles: true }));
221
- el.dispatchEvent(new Event('change', { bubbles: true }));
222
- return { ok: true, value: el.value }; })()`;
223
- const r = await rustRequest('evalAsync', { label: t.label, js: `return JSON.stringify(${script});` }) as { json: string };
224
- const inner = JSON.parse(r.json || 'null') as { error?: string; ok?: boolean } | null;
225
- if (inner?.error) throw new Error(inner.error);
226
- return inner;
227
- }
228
- case 'cdpEval': {
229
- // CSP-immune eval — WKWebView callAsyncJavaScript (native compilation, awaits promises).
230
- // The bridge server falls back to this when a page's CSP blocks the content-layer eval().
231
- const t = byTabId(params.tabId);
232
- const expression = String(params.expression || '');
233
- const body = `const __r = await (${expression}); return JSON.stringify(__r === undefined ? null : __r);`;
234
- const timeoutMs = Number((params as { timeoutMs?: number }).timeoutMs || 30000);
235
- try {
236
- const r = await rustRequest('evalAsync', { label: t.label, js: body }, timeoutMs) as { json: string };
237
- return { result: JSON.parse(r.json || 'null') };
238
- } catch (e) {
239
- return { result: null, error: (e as Error).message };
240
- }
241
- }
242
- case 'cdpInsertText': {
243
- // Controlled text insertion into a contenteditable / input WITHOUT ever submitting.
244
- // The extension's real path is CDP Input.insertText (we have no CDP); this replicates it
245
- // via focus + beforeinput/insertText/input events. It deliberately dispatches NO Enter/
246
- // keydown and never clicks a submit control — a spike delegation auto-submitted a comment
247
- // because the earlier execCommand workaround let a stray Enter through; this closes that.
248
- const t = byTabId(params.tabId);
249
- const selector = (params as { selector?: string }).selector;
250
- const text = String((params as { text?: string }).text ?? '');
251
- const clear = !!(params as { clear?: boolean }).clear;
252
- const body = `
253
- const el = ${selector ? `document.querySelector(${JSON.stringify(selector)})` : 'document.activeElement'};
254
- if (!el) return JSON.stringify({ error: 'insert target not found' });
255
- el.focus();
256
- const editable = el.isContentEditable;
257
- if (${clear}) {
258
- if (editable) { const r = document.createRange(); r.selectNodeContents(el); const s = getSelection(); s.removeAllRanges(); s.addRange(r); document.execCommand('delete'); }
259
- else { const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype; const set = Object.getOwnPropertyDescriptor(proto,'value')?.set; set ? set.call(el,'') : (el.value=''); }
260
- }
261
- el.dispatchEvent(new InputEvent('beforeinput', { bubbles: true, cancelable: true, inputType: 'insertText', data: ${JSON.stringify(text)} }));
262
- if (editable) {
263
- if (!document.execCommand('insertText', false, ${JSON.stringify(text)})) el.textContent += ${JSON.stringify(text)};
264
- } else {
265
- const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
266
- const set = Object.getOwnPropertyDescriptor(proto,'value')?.set; const nv = (el.value||'') + ${JSON.stringify(text)};
267
- set ? set.call(el, nv) : (el.value = nv);
268
- }
269
- el.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: ${JSON.stringify(text)} }));
270
- return JSON.stringify({ ok: true, value: editable ? el.innerText : el.value, submitted: false });`;
271
- const r = await rustRequest('evalAsync', { label: t.label, js: body }) as { json: string };
272
- const out = JSON.parse(r.json || 'null') as { error?: string };
273
- if (out?.error) throw new Error(out.error);
274
- return out;
275
- }
276
- case 'getGroup': return { groupId: null };
277
- case 'closeGroup': return { success: true };
278
- case 'listExtensions': return [];
279
- default:
280
- throw new Error(`unsupported on appwrap-desktop: ${msg.cmd}`);
281
- }
282
- }
283
-
284
99
  // ---------------------------------------------------------------------------
285
100
  // Bridge server WS client (the extension background's ws.ts analogue)
286
101
  // ---------------------------------------------------------------------------
@@ -298,7 +113,7 @@ function connectServer() {
298
113
  const parsed = JSON.parse(String(ev.data));
299
114
  if (parsed.type === 'pong' || parsed.type === 'reload' || parsed.type === 'recording_saved') return;
300
115
  msg = parsed;
301
- const result = await handleCommand(msg!);
116
+ const result = await driver.handleCommand(msg!);
302
117
  log(`cmd=${msg!.cmd} ok`);
303
118
  serverWs!.send(JSON.stringify({ id: msg!.id, result }));
304
119
  } catch (e) {
@@ -0,0 +1,330 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "bitflags"
7
+ version = "2.13.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
10
+
11
+ [[package]]
12
+ name = "block2"
13
+ version = "0.6.2"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
16
+ dependencies = [
17
+ "objc2",
18
+ ]
19
+
20
+ [[package]]
21
+ name = "dispatch2"
22
+ version = "0.3.1"
23
+ source = "registry+https://github.com/rust-lang/crates.io-index"
24
+ checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
25
+ dependencies = [
26
+ "bitflags",
27
+ "objc2",
28
+ ]
29
+
30
+ [[package]]
31
+ name = "itoa"
32
+ version = "1.0.18"
33
+ source = "registry+https://github.com/rust-lang/crates.io-index"
34
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
35
+
36
+ [[package]]
37
+ name = "libc"
38
+ version = "0.2.186"
39
+ source = "registry+https://github.com/rust-lang/crates.io-index"
40
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
41
+
42
+ [[package]]
43
+ name = "memchr"
44
+ version = "2.8.3"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
47
+
48
+ [[package]]
49
+ name = "objc2"
50
+ version = "0.6.4"
51
+ source = "registry+https://github.com/rust-lang/crates.io-index"
52
+ checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
53
+ dependencies = [
54
+ "objc2-encode",
55
+ ]
56
+
57
+ [[package]]
58
+ name = "objc2-app-kit"
59
+ version = "0.3.2"
60
+ source = "registry+https://github.com/rust-lang/crates.io-index"
61
+ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
62
+ dependencies = [
63
+ "bitflags",
64
+ "block2",
65
+ "libc",
66
+ "objc2",
67
+ "objc2-cloud-kit",
68
+ "objc2-core-data",
69
+ "objc2-core-foundation",
70
+ "objc2-core-graphics",
71
+ "objc2-core-image",
72
+ "objc2-core-text",
73
+ "objc2-core-video",
74
+ "objc2-foundation",
75
+ "objc2-quartz-core",
76
+ ]
77
+
78
+ [[package]]
79
+ name = "objc2-cloud-kit"
80
+ version = "0.3.2"
81
+ source = "registry+https://github.com/rust-lang/crates.io-index"
82
+ checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
83
+ dependencies = [
84
+ "bitflags",
85
+ "objc2",
86
+ "objc2-foundation",
87
+ ]
88
+
89
+ [[package]]
90
+ name = "objc2-core-data"
91
+ version = "0.3.2"
92
+ source = "registry+https://github.com/rust-lang/crates.io-index"
93
+ checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
94
+ dependencies = [
95
+ "bitflags",
96
+ "objc2",
97
+ "objc2-foundation",
98
+ ]
99
+
100
+ [[package]]
101
+ name = "objc2-core-foundation"
102
+ version = "0.3.2"
103
+ source = "registry+https://github.com/rust-lang/crates.io-index"
104
+ checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
105
+ dependencies = [
106
+ "bitflags",
107
+ "dispatch2",
108
+ "objc2",
109
+ ]
110
+
111
+ [[package]]
112
+ name = "objc2-core-graphics"
113
+ version = "0.3.2"
114
+ source = "registry+https://github.com/rust-lang/crates.io-index"
115
+ checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
116
+ dependencies = [
117
+ "bitflags",
118
+ "dispatch2",
119
+ "objc2",
120
+ "objc2-core-foundation",
121
+ "objc2-io-surface",
122
+ ]
123
+
124
+ [[package]]
125
+ name = "objc2-core-image"
126
+ version = "0.3.2"
127
+ source = "registry+https://github.com/rust-lang/crates.io-index"
128
+ checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
129
+ dependencies = [
130
+ "objc2",
131
+ "objc2-foundation",
132
+ ]
133
+
134
+ [[package]]
135
+ name = "objc2-core-text"
136
+ version = "0.3.2"
137
+ source = "registry+https://github.com/rust-lang/crates.io-index"
138
+ checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
139
+ dependencies = [
140
+ "bitflags",
141
+ "objc2",
142
+ "objc2-core-foundation",
143
+ "objc2-core-graphics",
144
+ ]
145
+
146
+ [[package]]
147
+ name = "objc2-core-video"
148
+ version = "0.3.2"
149
+ source = "registry+https://github.com/rust-lang/crates.io-index"
150
+ checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
151
+ dependencies = [
152
+ "bitflags",
153
+ "objc2",
154
+ "objc2-core-foundation",
155
+ "objc2-core-graphics",
156
+ "objc2-io-surface",
157
+ ]
158
+
159
+ [[package]]
160
+ name = "objc2-encode"
161
+ version = "4.1.0"
162
+ source = "registry+https://github.com/rust-lang/crates.io-index"
163
+ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
164
+
165
+ [[package]]
166
+ name = "objc2-foundation"
167
+ version = "0.3.2"
168
+ source = "registry+https://github.com/rust-lang/crates.io-index"
169
+ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
170
+ dependencies = [
171
+ "bitflags",
172
+ "block2",
173
+ "libc",
174
+ "objc2",
175
+ "objc2-core-foundation",
176
+ ]
177
+
178
+ [[package]]
179
+ name = "objc2-io-surface"
180
+ version = "0.3.2"
181
+ source = "registry+https://github.com/rust-lang/crates.io-index"
182
+ checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
183
+ dependencies = [
184
+ "bitflags",
185
+ "objc2",
186
+ "objc2-core-foundation",
187
+ ]
188
+
189
+ [[package]]
190
+ name = "objc2-javascript-core"
191
+ version = "0.3.2"
192
+ source = "registry+https://github.com/rust-lang/crates.io-index"
193
+ checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586"
194
+ dependencies = [
195
+ "objc2",
196
+ "objc2-core-foundation",
197
+ ]
198
+
199
+ [[package]]
200
+ name = "objc2-quartz-core"
201
+ version = "0.3.2"
202
+ source = "registry+https://github.com/rust-lang/crates.io-index"
203
+ checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
204
+ dependencies = [
205
+ "bitflags",
206
+ "objc2",
207
+ "objc2-foundation",
208
+ ]
209
+
210
+ [[package]]
211
+ name = "objc2-security"
212
+ version = "0.3.2"
213
+ source = "registry+https://github.com/rust-lang/crates.io-index"
214
+ checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a"
215
+ dependencies = [
216
+ "bitflags",
217
+ "objc2",
218
+ "objc2-core-foundation",
219
+ ]
220
+
221
+ [[package]]
222
+ name = "objc2-web-kit"
223
+ version = "0.3.2"
224
+ source = "registry+https://github.com/rust-lang/crates.io-index"
225
+ checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f"
226
+ dependencies = [
227
+ "bitflags",
228
+ "block2",
229
+ "objc2",
230
+ "objc2-app-kit",
231
+ "objc2-core-foundation",
232
+ "objc2-foundation",
233
+ "objc2-javascript-core",
234
+ "objc2-security",
235
+ ]
236
+
237
+ [[package]]
238
+ name = "proc-macro2"
239
+ version = "1.0.106"
240
+ source = "registry+https://github.com/rust-lang/crates.io-index"
241
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
242
+ dependencies = [
243
+ "unicode-ident",
244
+ ]
245
+
246
+ [[package]]
247
+ name = "quote"
248
+ version = "1.0.46"
249
+ source = "registry+https://github.com/rust-lang/crates.io-index"
250
+ checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
251
+ dependencies = [
252
+ "proc-macro2",
253
+ ]
254
+
255
+ [[package]]
256
+ name = "serde"
257
+ version = "1.0.228"
258
+ source = "registry+https://github.com/rust-lang/crates.io-index"
259
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
260
+ dependencies = [
261
+ "serde_core",
262
+ ]
263
+
264
+ [[package]]
265
+ name = "serde_core"
266
+ version = "1.0.228"
267
+ source = "registry+https://github.com/rust-lang/crates.io-index"
268
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
269
+ dependencies = [
270
+ "serde_derive",
271
+ ]
272
+
273
+ [[package]]
274
+ name = "serde_derive"
275
+ version = "1.0.228"
276
+ source = "registry+https://github.com/rust-lang/crates.io-index"
277
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
278
+ dependencies = [
279
+ "proc-macro2",
280
+ "quote",
281
+ "syn",
282
+ ]
283
+
284
+ [[package]]
285
+ name = "serde_json"
286
+ version = "1.0.150"
287
+ source = "registry+https://github.com/rust-lang/crates.io-index"
288
+ checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
289
+ dependencies = [
290
+ "itoa",
291
+ "memchr",
292
+ "serde",
293
+ "serde_core",
294
+ "zmij",
295
+ ]
296
+
297
+ [[package]]
298
+ name = "syn"
299
+ version = "2.0.118"
300
+ source = "registry+https://github.com/rust-lang/crates.io-index"
301
+ checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
302
+ dependencies = [
303
+ "proc-macro2",
304
+ "quote",
305
+ "unicode-ident",
306
+ ]
307
+
308
+ [[package]]
309
+ name = "unicode-ident"
310
+ version = "1.0.24"
311
+ source = "registry+https://github.com/rust-lang/crates.io-index"
312
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
313
+
314
+ [[package]]
315
+ name = "webview-control"
316
+ version = "0.1.0"
317
+ dependencies = [
318
+ "block2",
319
+ "objc2",
320
+ "objc2-app-kit",
321
+ "objc2-foundation",
322
+ "objc2-web-kit",
323
+ "serde_json",
324
+ ]
325
+
326
+ [[package]]
327
+ name = "zmij"
328
+ version = "1.0.21"
329
+ source = "registry+https://github.com/rust-lang/crates.io-index"
330
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
@@ -0,0 +1,28 @@
1
+ # webview-control — appwrap's generalized, automation-agnostic native webview primitive.
2
+ # Carved out of src-tauri/src/bridge_mac.rs (Phase 1). Owns per-profile persistent+isolated
3
+ # WKWebsiteDataStore windows, CSP-immune eval, snapshot, the bridgeShim WKScriptMessageHandler,
4
+ # and the SSO-popup delegate (popup module). It emits RAW script-message payloads + RAW eval
5
+ # results via callbacks — it holds NO bod-browser protocol / transport knowledge (that stays in
6
+ # the control-channel adapter, bridge_mac.rs).
7
+ #
8
+ # objc2* versions MUST match src-tauri/Cargo.toml exactly — the same WKWebView pointer crosses the
9
+ # crate boundary (pw.inner() as *mut WKWebView), so a version mismatch would make that cast UB.
10
+ [package]
11
+ name = "webview-control"
12
+ version = "0.1.0"
13
+ edition = "2021"
14
+
15
+ [lib]
16
+ # macOS-only today; a WebKitGtkBackend is a future slot behind the same seam (see ARCHITECTURE.md).
17
+ name = "webview_control"
18
+ path = "src/lib.rs"
19
+
20
+ [dependencies]
21
+
22
+ [target.'cfg(target_os = "macos")'.dependencies]
23
+ serde_json = "1"
24
+ objc2 = "0.6"
25
+ objc2-foundation = "0.3"
26
+ objc2-app-kit = "0.3"
27
+ objc2-web-kit = "0.3"
28
+ block2 = "0.6"