@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.
- package/package.json +1 -1
- package/runtime/App_Resources/Android/src/main/AndroidManifest.xml +3 -0
- package/runtime/app/shell/capabilities.manifest.ts +65 -3
- package/runtime/app/shell/custom-webview.android.ts +9 -0
- package/runtime/app/shell/handlers-android.ts +54 -9
- package/runtime/app/shell/handlers-extended.ts +82 -4
- package/runtime/app/shell/handlers-system.ts +48 -1
- package/runtime/app/shell/handlers-widget.ts +97 -0
- package/runtime/app/shell/notif-identity.ts +45 -0
- package/runtime/app/shell/web-quirks.ts +18 -0
- package/runtime/modules-native/widget/App_Resources/Android/src/main/java/cc/livx/appwrap/AppwrapWidgetProvider.kt +79 -0
- package/runtime/modules-native/widget/App_Resources/Android/src/main/res/drawable/appwrap_badge_bg.xml +7 -0
- package/runtime/modules-native/widget/App_Resources/Android/src/main/res/layout/appwrap_widget.xml +70 -0
- package/runtime/modules-native/widget/App_Resources/Android/src/main/res/values/appwrap_widget_styles.xml +40 -0
- package/runtime/modules-native/widget/App_Resources/Android/src/main/res/xml/appwrap_widget_info.xml +13 -0
- package/runtime/modules-native/widget/App_Resources/iOS/extensions/AppwrapWidget/AppwrapWidget.entitlements +10 -0
- package/runtime/modules-native/widget/App_Resources/iOS/extensions/AppwrapWidget/AppwrapWidget.swift +275 -0
- package/runtime/modules-native/widget/App_Resources/iOS/extensions/AppwrapWidget/Info.plist +25 -0
- package/runtime/modules-native/widget/App_Resources/iOS/extensions/AppwrapWidget/extension.json +9 -0
- package/runtime/modules-native/widget/App_Resources/iOS/src/AppwrapWidgetReload.swift +18 -0
- package/runtime/references.d.ts +1 -0
- package/runtime/tests/notif-identity.test.ts +43 -0
- package/runtime-desktop/chrome/appwrap-browser-chrome.html +70 -5
- package/runtime-desktop/crates/webview-control/src/lib.rs +2 -1
- package/runtime-desktop/crates/webview-control/src/wkwebview_backend.rs +22 -9
- package/runtime-desktop/src-tauri/src/browser_tab.rs +379 -20
- package/runtime-desktop/src-tauri/src/main.rs +191 -11
- package/src/cli.ts +74 -0
- package/src/config.ts +8 -1
- package/src/plugin/host.ts +5 -0
- 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
|
-
|
|
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
|
-
|
|
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,
|
|
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:
|
|
131
|
-
// already registered
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
ucc.
|
|
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() };
|