Skip to main content

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

  1. Load the standalone visitor URL in <web-view> and make sure the page has the agreed window.webUni.postMessage adapter.
  2. Bind @message (App-vue) or the target base's @onPostMessage event (App-nvue).
  3. 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:

  1. The App base injects an object with postMessage before the page becomes interactive.
  2. The page loads a self-hosted copy of DCloud's official WebView SDK and aliases its uni.postMessage API after UniAppJSBridgeReady.

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 https visitor hosts before setting src, 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.origin and event.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-click message;
  • the JSON has no permission field 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.

References