uni-app WebView upload click notifications
This tutorial explains how to embed the standalone visitor page in a uni-app
<web-view> and receive a notification when the visitor taps the attachment
entry. It applies to uni-app App-vue and App-nvue projects.
The notification only describes a click. The visitor page still owns the file input, file reading, and upload flow. The native app must not open a second picker or treat this event as a permission result.
Minimal steps
- Load the standalone visitor URL in
<web-view>and make sure the page has the agreedwindow.webUni.postMessageadapter. - Bind
@message(App-vue) or the target base's@onPostMessageevent (App-nvue). - Validate and record
visitor-upload-click; stay passive and do not open a second picker or request a permission because of this notification.
What the integration does
Visitor taps the attachment entry
→ visitor page sends { type: "visitor-upload-click" }
→ visitor page continues input.click()
→ the WebView / operating system opens the file picker
→ the visitor page handles change and upload
Use a direct visitor URL and replace the host and app ID with your values. Add
source=webview when you want to hide the visitor page's back button:
https://page.visitor-chat.com/direct/{YOUR_APP_ID}?source=webview
Protocol
The visitor page calls the agreed window.webUni adapter. Keep the data
envelope intact:
window.webUni?.postMessage({
data: { type: 'visitor-upload-click' },
});
The default payload is exactly:
{"type":"visitor-upload-click"}
In App-vue, the callback normally exposes the payload at
event.detail.data, and some base versions wrap it in an array. The message
does not contain a file, credential, upload result, or operating-system
permission state. permission is reserved for forward compatibility and is
not sent by the current attachment entry. If you validate it, accept only
microphone, camera, or album; ignore unknown values.
The event is best-effort and synchronous. There is no ACK. If the bridge is missing or throws, the visitor page must continue its own file-selection flow.
App-vue integration
Bind @message on the WebView and validate unknown data before consuming it:
<template>
<web-view :src="visitorUrl" @message="handleWebViewMessage" />
</template>
<script setup lang="ts">
const visitorUrl = 'https://page.visitor-chat.com/direct/YOUR_APP_ID?source=webview';
type UploadPermission = 'microphone' | 'camera' | 'album';
interface UploadClickMessage {
type: 'visitor-upload-click';
permission?: UploadPermission;
}
const permissions = new Set<UploadPermission>([
'microphone',
'camera',
'album',
]);
function isUploadClickMessage(value: unknown): value is UploadClickMessage {
if (!value || typeof value !== 'object') return false;
const candidate = value as Record<string, unknown>;
return candidate.type === 'visitor-upload-click' &&
(candidate.permission === undefined ||
(typeof candidate.permission === 'string' &&
permissions.has(candidate.permission as UploadPermission)));
}
function normalizeMessages(value: unknown): unknown[] {
if (Array.isArray(value)) {
return value.flatMap((entry) => normalizeMessages(entry));
}
if (value && typeof value === 'object' && !('type' in value) && 'data' in value) {
return normalizeMessages((value as { data: unknown }).data);
}
return [value];
}
function handleWebViewMessage(event: { detail?: { data?: unknown } }) {
const messages = normalizeMessages(event.detail?.data);
for (const message of messages) {
if (!isUploadClickMessage(message)) continue;
// Record state or telemetry only. Do not call uni.chooseImage/chooseFile.
console.info('[visitor] upload click', message);
}
}
</script>
event.detail.data may be a single object or an array depending on the
uni-app base version. The demo also unwraps an extra data envelope. Normalize
these forms, ignore empty values and unknown type values, and do not use a
broad type assertion as validation.
App-nvue integration
Some App-nvue bases expose a real-time event named @onPostMessage instead:
The handler below reuses isUploadClickMessage and normalizeMessages from
the App-vue example.
<template>
<web-view
:src="visitorUrl"
style="flex: 1"
@onPostMessage="handleNvueWebViewMessage"
/>
</template>
<script setup lang="ts">
function handleNvueWebViewMessage(event: { detail?: unknown }) {
const detail = event?.detail;
const rawData =
detail && typeof detail === 'object' && 'data' in detail
? (detail as { data?: unknown }).data ?? detail
: detail;
for (const message of normalizeMessages(rawData)) {
if (isUploadClickMessage(message)) {
console.info('[visitor] upload click', message);
}
}
}
</script>
The exact event name, payload location, and real-time behavior depend on the
uni-app version, HBuilderX version, and debug base. Confirm the generated
component types and test on the target device. H5 browser behavior does not
prove App-Plus behavior. If a target App-vue base only dispatches @message
during page-back, destroy, or share lifecycle events, do not treat it as a
real-time click channel; use a real-time API supported by that base or switch
to the App-nvue integration.
Make window.webUni available
webUni is an integration contract, not a browser or uni-app built-in. Agree
with the visitor-page owner on one of these options:
- The App base injects an object with
postMessagebefore the page becomes interactive. - The page loads a self-hosted copy of DCloud's official WebView SDK and
aliases its
uni.postMessageAPI afterUniAppJSBridgeReady.
Example adapter (the SDK path is only an example):
<script src="/vendor/uni.webview.1.5.8.js"></script>
<script>
(() => {
function install() {
if (typeof window.webUni?.postMessage === 'function') return;
const postMessage =
window.uni?.postMessage ?? window.uni?.webView?.postMessage;
if (typeof postMessage !== 'function') return;
window.webUni = {
postMessage: postMessage.bind(
window.uni?.postMessage ? window.uni : window.uni?.webView,
),
};
}
document.addEventListener('UniAppJSBridgeReady', install, { once: true });
install();
})();
</script>
Do not replace an existing webUni adapter, implement two competing sending
paths, or use window.parent.postMessage as the production App-Plus protocol.
If you inject with HTML5+ evalJS or appendJsFile, restrict injection to an
approved visitor domain. evalJS() returning does not prove that the page
installed the adapter; verify with a page-side ready log or a controlled test.
Clicks that occur before the bridge is ready can be missed. The visitor page must still open its own file input. If every click must be delivered, delay the attachment entry until the bridge is ready or design a bounded queue explicitly.
Security and failure handling
- Allow only approved
httpsvisitor hosts before settingsrc, and validate subsequent navigation as well as the initial URL. - App-vue callbacks generally do not expose a browser
event.origin; URL and navigation allow-lists are therefore the primary source boundary in App-Plus. - In H5 simulation, validate both
event.originandevent.source. This is a browser fixture and is not a substitute for App-Plus message isolation. - Ignore malformed payloads and log only non-sensitive diagnostics.
- A cancelled picker is a normal branch, not an upload error.
- One user click produces at most one notification. Receiving it must never trigger another picker or a permission prompt.
Local demo and verification
The uniapp-demo sample project contains an App-vue page, a local mock visitor,
payload normalization, origin/source checks, and a message history. From that
project directory, run:
pnpm install
./node_modules/.bin/uni --host 127.0.0.1 --port 5188
Open the printed H5 URL and click 选择附件并通知宿主 in the mock visitor. The expected result is:
- the host records one
visitor-upload-clickmessage; - the JSON has no
permissionfield by default; - the mock visitor still calls its HTML
<input type="file">; - cancelling the picker does not create an error or retract the notification.
To load a real visitor page in H5, set VITE_VISITOR_URL before starting:
VITE_VISITOR_URL="https://page.visitor-chat.com/direct/YOUR_APP_ID?source=webview" \
./node_modules/.bin/uni --host 127.0.0.1 --port 5188
For an App-Plus build:
./node_modules/.bin/uni build -p app-plus
public/mock-visitor.html is provided by the H5 dev server and is not
automatically packaged as an App-Plus local page. Use an accessible visitor URL,
or move local HTML into hybrid/html or static according to the target
project's packaging rules.
Before release, test on the target iOS and Android devices and record the OS, HBuilderX version, debug-base version, WebView callback timing, picker behavior, and navigation allow-list result. H5 build output alone is not device evidence.
Out of scope
This protocol does not define native file selection or upload, camera/album/
microphone permission flows, upload progress or cancellation, ACKs, or a
general-purpose native-to-WebView bridge. Design those capabilities as separate
protocols instead of expanding visitor-upload-click.