@livx.cc/appwrap 0.47.3 → 0.48.1

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.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/runtime/App_Resources/Android/src/main/AndroidManifest.xml +3 -0
  3. package/runtime/app/shell/capabilities.manifest.ts +65 -3
  4. package/runtime/app/shell/custom-webview.android.ts +9 -0
  5. package/runtime/app/shell/handlers-android.ts +54 -9
  6. package/runtime/app/shell/handlers-extended.ts +82 -4
  7. package/runtime/app/shell/handlers-system.ts +48 -1
  8. package/runtime/app/shell/handlers-widget.ts +97 -0
  9. package/runtime/app/shell/notif-identity.ts +45 -0
  10. package/runtime/app/shell/web-quirks.ts +18 -0
  11. package/runtime/modules-native/widget/App_Resources/Android/src/main/java/cc/livx/appwrap/AppwrapWidgetProvider.kt +79 -0
  12. package/runtime/modules-native/widget/App_Resources/Android/src/main/res/drawable/appwrap_badge_bg.xml +7 -0
  13. package/runtime/modules-native/widget/App_Resources/Android/src/main/res/layout/appwrap_widget.xml +70 -0
  14. package/runtime/modules-native/widget/App_Resources/Android/src/main/res/values/appwrap_widget_styles.xml +40 -0
  15. package/runtime/modules-native/widget/App_Resources/Android/src/main/res/xml/appwrap_widget_info.xml +13 -0
  16. package/runtime/modules-native/widget/App_Resources/iOS/extensions/AppwrapWidget/AppwrapWidget.entitlements +10 -0
  17. package/runtime/modules-native/widget/App_Resources/iOS/extensions/AppwrapWidget/AppwrapWidget.swift +275 -0
  18. package/runtime/modules-native/widget/App_Resources/iOS/extensions/AppwrapWidget/Info.plist +25 -0
  19. package/runtime/modules-native/widget/App_Resources/iOS/extensions/AppwrapWidget/extension.json +9 -0
  20. package/runtime/modules-native/widget/App_Resources/iOS/src/AppwrapWidgetReload.swift +18 -0
  21. package/runtime/references.d.ts +1 -0
  22. package/runtime/tests/notif-identity.test.ts +43 -0
  23. package/runtime-desktop/chrome/appwrap-browser-chrome.html +70 -5
  24. package/runtime-desktop/crates/webview-control/src/lib.rs +2 -1
  25. package/runtime-desktop/crates/webview-control/src/wkwebview_backend.rs +22 -9
  26. package/runtime-desktop/src-tauri/src/browser_tab.rs +379 -20
  27. package/runtime-desktop/src-tauri/src/main.rs +191 -11
  28. package/src/cli.ts +74 -0
  29. package/src/config.ts +8 -1
  30. package/src/plugin/host.ts +5 -0
  31. package/src/plugin/types.ts +1 -0
@@ -40,11 +40,12 @@
40
40
  background: var(--field-bg); color: var(--fg); font-size: 13px; outline: none;
41
41
  }
42
42
  #addr:focus { border-color: #4c8bf5; }
43
- #profile {
43
+ #profile, #newprof {
44
44
  flex: none; height: 28px; max-width: 160px;
45
45
  border: 1px solid var(--field-border); border-radius: 7px;
46
46
  background: var(--field-bg); color: var(--fg); font-size: 12px; padding: 0 4px;
47
47
  }
48
+ #newprof:focus { border-color: #4c8bf5; outline: none; }
48
49
  </style>
49
50
  </head>
50
51
  <body>
@@ -55,10 +56,17 @@
55
56
  <input id="addr" type="text" spellcheck="false" autocomplete="off"
56
57
  placeholder="Enter address (e.g. 127.0.0.1:3000)" />
57
58
  <select id="profile" title="Profile"></select>
59
+ <input id="newprof" type="text" spellcheck="false" autocomplete="off"
60
+ placeholder="profile name — Enter" style="display:none" />
58
61
  </div>
59
62
  <script>
60
63
  (function () {
61
- var id = new URLSearchParams(location.search).get('id') || '';
64
+ // id + profile arrive via hash fragment (#id=…&profile=…) — a query (?…) breaks Tauri's App-asset
65
+ // resolution. Read hash first, fall back to search for robustness.
66
+ var hp = new URLSearchParams(location.hash.replace(/^#/, ''));
67
+ var sp = new URLSearchParams(location.search);
68
+ var id = hp.get('id') || sp.get('id') || '';
69
+ var initProfile = hp.get('profile') || sp.get('profile') || 'default';
62
70
  function invoke(cmd, args) {
63
71
  var core = window.__TAURI__ && window.__TAURI__.core;
64
72
  if (!core) return Promise.reject(new Error('Tauri core unavailable'));
@@ -85,17 +93,71 @@
85
93
  if (url) invoke('browser_tab_navigate', { id: id, url: url });
86
94
  });
87
95
 
96
+ var NEW_PROFILE = '__new__'; // sentinel option: reveal an inline input, then switch to the name
97
+ var current = initProfile; // the profile actually shown (to revert the <select> on cancel)
98
+ var newprof = document.getElementById('newprof');
99
+
100
+ function addProfileOption(name) {
101
+ if (!name) return;
102
+ for (var i = 0; i < profileSel.options.length; i++) {
103
+ if (profileSel.options[i].value === name) return; // already listed
104
+ }
105
+ var opt = document.createElement('option');
106
+ opt.value = name; opt.textContent = name;
107
+ profileSel.insertBefore(opt, profileSel.lastChild); // before the "+ New profile…" item
108
+ }
109
+
110
+ function switchProfile(name) {
111
+ // Switching recreates the webview with a fresh, isolated store; the name is persisted on first
112
+ // use (uuid_for) so it reappears in the list next launch.
113
+ addProfileOption(name);
114
+ profileSel.value = name;
115
+ current = name;
116
+ invoke('browser_subwindow_switch_profile', { id: id, profileId: name });
117
+ }
118
+
119
+ // Inline "+ New profile" flow — WKWebView has no window.prompt (no text-input panel delegate), so
120
+ // reveal a small input instead of prompting. Enter commits, Escape/empty-blur cancels.
121
+ function beginNewProfile() {
122
+ profileSel.style.display = 'none';
123
+ newprof.style.display = '';
124
+ newprof.value = '';
125
+ newprof.focus();
126
+ }
127
+ function endNewProfile(commit) {
128
+ if (newprof.style.display === 'none') return; // already closed (guard double-fire)
129
+ var name = (newprof.value || '').trim();
130
+ newprof.style.display = 'none';
131
+ profileSel.style.display = '';
132
+ profileSel.value = current; // reset the sentinel selection
133
+ if (commit && name && name !== current) switchProfile(name);
134
+ }
135
+ newprof.addEventListener('keydown', function (e) {
136
+ if (e.key === 'Enter') endNewProfile(true);
137
+ else if (e.key === 'Escape') endNewProfile(false);
138
+ });
139
+ newprof.addEventListener('blur', function () { endNewProfile(true); });
140
+
88
141
  profileSel.addEventListener('change', function () {
89
- invoke('browser_subwindow_switch_profile', { id: id, profileId: profileSel.value });
142
+ var val = profileSel.value;
143
+ if (val === NEW_PROFILE) { beginNewProfile(); return; }
144
+ current = val;
145
+ invoke('browser_subwindow_switch_profile', { id: id, profileId: val });
90
146
  });
91
147
 
92
- // Populate the profile dropdown.
148
+ // Populate the profile dropdown, then append the "+ New profile…" sentinel.
93
149
  invoke('browser_list_profiles').then(function (profiles) {
94
150
  (profiles || ['default']).forEach(function (p) {
95
151
  var opt = document.createElement('option');
96
152
  opt.value = p; opt.textContent = p;
97
153
  profileSel.appendChild(opt);
98
154
  });
155
+ var add = document.createElement('option');
156
+ add.value = NEW_PROFILE; add.textContent = '+ New profile…';
157
+ profileSel.appendChild(add);
158
+ // Reflect the window's ACTUAL mounted profile (may be non-default on a restored window).
159
+ addProfileOption(initProfile);
160
+ profileSel.value = initProfile;
99
161
  }).catch(function (e) { console.error('list profiles failed', e); });
100
162
 
101
163
  // Keep the address bar in sync with the controlled webview (skip while the user is editing it).
@@ -112,8 +174,11 @@
112
174
  } catch (_) {}
113
175
  }).catch(function () {});
114
176
  }
115
- setInterval(poll, 1000);
177
+ var pollTimer = setInterval(poll, 1000);
116
178
  poll();
179
+ // Stop polling as the window unloads — a poll's current_url eval racing the native teardown blocks
180
+ // the closing window (the webview is gone, so the eval never resolves). Fixes the close-hang.
181
+ window.addEventListener('pagehide', function () { clearInterval(pollTimer); });
117
182
  })();
118
183
  </script>
119
184
  </body>
@@ -90,7 +90,8 @@ pub enum Mount {
90
90
  #[cfg(target_os = "macos")]
91
91
  pub use wkwebview_backend::{
92
92
  close, create_profile_window, create_webview, devtools_enabled, eval_async, go_back, go_forward,
93
- inject_script, install_message_handler, list_webviews, navigate, profile_webview, reload,
93
+ inject_script, install_message_handler, install_named_message_handler, list_webviews, navigate,
94
+ profile_webview, reload,
94
95
  set_inspectable, set_mount, snapshot, user_close, CreateResult, CreateSpec, MessageSink,
95
96
  WebviewInfo,
96
97
  };
@@ -123,19 +123,28 @@ define_class!(
123
123
  /// the legacy tauri-window path — the profile-window path installs it on the config's UCC before
124
124
  /// creation). The UCC retains the handler, so no external retention is needed here. Main-thread only.
125
125
  pub unsafe fn install_message_handler(wk: *mut WKWebView, mtm: MainThreadMarker, on_message: MessageSink) {
126
+ install_named_message_handler(wk, mtm, "bridgeShim", on_message);
127
+ }
128
+
129
+ /// Install a WKScriptMessageHandler under `name` on a WKWebView, so page JS reaches native via
130
+ /// `webkit.messageHandlers.<name>.postMessage(str)`. Origin-agnostic and NOT command-ACL-gated —
131
+ /// the ONLY reliable page→native path for a REMOTE-served page (Tauri command IPC is ACL-blocked
132
+ /// for remote origins; ws/http/custom-scheme are mixed-content-blocked from https). Main-thread only.
133
+ pub unsafe fn install_named_message_handler(
134
+ wk: *mut WKWebView,
135
+ mtm: MainThreadMarker,
136
+ name: &str,
137
+ on_message: MessageSink,
138
+ ) {
126
139
  let wk = &*wk;
127
140
  let handler = MsgHandler::alloc(mtm).set_ivars(MsgHandlerIvars { on_message });
128
141
  let handler: Retained<MsgHandler> = msg_send![super(handler), init];
129
142
  let ucc = wk.configuration().userContentController();
130
- // Idempotent per UCC: WKUserContentController throws NSInvalidArgumentException if a handler is
131
- // already registered for a name. A window can already carry `bridgeShim` (a second plugin
132
- // attaching onMessage, a browser tab, or the env-gated bridge). removeScriptMessageHandlerForName
133
- // is safe when none is present, so remove-then-add makes re-install a no-op instead of a crash.
134
- ucc.removeScriptMessageHandlerForName(&NSString::from_str("bridgeShim"));
135
- ucc.addScriptMessageHandler_name(
136
- ProtocolObject::from_ref(&*handler),
137
- &NSString::from_str("bridgeShim"),
138
- );
143
+ // Idempotent per UCC: addScriptMessageHandler throws NSInvalidArgumentException if `name` is
144
+ // already registered; remove-then-add makes re-install a no-op instead of a crash.
145
+ let ns_name = NSString::from_str(name);
146
+ ucc.removeScriptMessageHandlerForName(&ns_name);
147
+ ucc.addScriptMessageHandler_name(ProtocolObject::from_ref(&*handler), &ns_name);
139
148
  }
140
149
 
141
150
  /// A manually-created profile window. Strong refs kept alive until close (or windowWillClose).
@@ -305,6 +314,10 @@ pub fn create_webview(
305
314
 
306
315
  let cfg = unsafe { WKWebViewConfiguration::new(mtm) };
307
316
  unsafe { cfg.setWebsiteDataStore(&store) };
317
+ // Allow programmatic window.open (the createWebViewWithConfiguration UI-delegate below hosts the
318
+ // child popup). Without this WKWebView blocks any window.open NOT fired synchronously in a click —
319
+ // which silently kills OAuth "Sign in with Google/…" popups that open AFTER an async round-trip.
320
+ unsafe { cfg.preferences().setJavaScriptCanOpenWindowsAutomatically(true) };
308
321
 
309
322
  // bridgeShim message handler + content bundle (documentStart) on this cfg's UCC.
310
323
  let ucc = unsafe { cfg.userContentController() };