Permissions in JavaScript: WebInto.app `permissions` Bridge

person Web2App Team calendar_today May 15, 2026
Permissions in JavaScript: WebInto.app `permissions` Bridge image

When your website runs inside WebInto.app, you may need to know whether the user has allowed notifications, location, microphone, or camera—or to open system settings if they previously denied access. The permissions bridge lets your page ask the same permission flow the app uses, without leaving the WebView.

This guide documents the permissions bridge: window.Native.call(method, payload, callback) — the third argument is a function the app invokes with the result (JSON string or already-parsed object, depending on the host). The payload you pass is the usual { action, payload } object described below.

If your template defines waitForNative, run it before the first call (same idea as the custom HTML no-internet screen guide). Other bridge tutorials: OneSignal, screen brightness.


When to use the permissions bridge

Goal Suggested action
Read current access without a prompt CHECK_PERMISSION
Show the system permission sheet (when allowed) ASK_FOR_PERMISSION
User should fix things in system settings OPEN_APP_SETTINGS
Ping that the bridge exists NONE

Native method name: permissions. The app uses one permission pipeline for both the WebView and this bridge, so CHECK / ASK stay aligned with what the shell already uses for geo, mic, camera, and notifications.


Payload (by action)

action payload passed inside { action, payload }
NONE null
CHECK_PERMISSION { "permission": "<TYPE>" }
ASK_FOR_PERMISSION { "permission": "<TYPE>" }
OPEN_APP_SETTINGS null

<TYPE> (case-insensitive in the app; normalize in JS if you like): NOTIFICATION | LOCATION | AUDIO | VIDEO — maps to remote notifications, location, microphone, and camera.

Unknown or missing permission on CHECK / ASK returns status: false with a message asking for one of the four types above.


Response shape

Parsed JSON: status (boolean), optional message, optional params.

Field Type Description
status boolean true when the call completed as expected (including many “user denied” outcomes for ASK).
message string or omitted Human-readable detail when status is false.
params object or omitted After CHECK or ASK, when status is true and params is present, expect permissionStatus.

params.permissionStatus

GRANTED | DENIED | DENIED_ALWAYS | NOT_DETERMINED | NOT_GRANTED

Value Meaning (plain language)
GRANTED Access is allowed.
DENIED User declined (or access not granted) this time; they may be asked again later depending on the OS.
DENIED_ALWAYS ASK_FOR_PERMISSION will not fix this—send the user to system settings.
NOT_DETERMINED No final choice yet. On check, often “not granted / not asked yet”. On ask, can mean the user dismissed the system sheet without choosing.
NOT_GRANTED Android-focused: the OS may not distinguish “not asked yet” vs “blocked like always-deny”. Treat as “not usable until the user acts” — often follow with ASK_FOR_PERMISSION or guide to settings; do not assume which case it is. On iOS, CHECK more often surfaces NOT_DETERMINED, DENIED, DENIED_ALWAYS, or GRANTED.

Bad JSON or unknown actionstatus: false and message explains.

How CHECK_PERMISSION maps into these strings

  • GrantedGRANTED
  • DeniedDENIED
  • DeniedAlwaysDENIED_ALWAYS
  • NotDeterminedNOT_DETERMINED
  • NotGrantedNOT_GRANTED (see table)

How ASK_FOR_PERMISSION maps

You still usually get status: true with permissionStatus set (denial is not a “transport error”):

  • User allows → GRANTED
  • Permanent-style denial → DENIED_ALWAYS
  • Ordinary denial → DENIED
  • User dismissed the sheet with no choice → NOT_DETERMINED
  • Unexpected failures → status: false and message

Simple flow

  1. CHECK_PERMISSION first.
  2. GRANTED → done.
  3. NOT_GRANTED, NOT_DETERMINED, DENIEDASK_FOR_PERMISSION (same permission type).
  4. DENIED_ALWAYS (from check or after ask) → short UI text, then OPEN_APP_SETTINGS (usually on a button, not auto-spam).
  5. After the user returns from settings, CHECK_PERMISSION again if you need a fresh state.

Example code

Uses window.Native.call(method, payload, callback) — the callback receives the JSON string (or object, depending on the host). The helpers below parse either shape.

const PERMISSIONS_METHOD = "permissions";

function permissionsParse(raw) {
  if (raw == null) return { _raw: raw };
  if (typeof raw === "object") return raw;
  try {
    return JSON.parse(raw);
  } catch (e) {
    return { _parseError: String(e), _raw: raw };
  }
}

function hasNative() {
  return !!(window.Native && typeof window.Native.call === "function");
}

function permissionsCall(payload) {
  return new Promise((resolve, reject) => {
    if (!hasNative()) {
      reject(new Error("window.Native.call missing"));
      return;
    }
    console.log("[permissions] calling:", payload);
    window.Native.call(PERMISSIONS_METHOD, payload, function (raw) {
      const res = permissionsParse(raw);
      console.log("[permissions] response:", res);
      resolve(res);
    });
  });
}

window.permissionsNone = async function () {
  return permissionsCall({ action: "NONE", payload: null });
};

window.permissionsCheck = async function (permission = "LOCATION") {
  const perm = String(permission || "LOCATION").toUpperCase();
  return permissionsCall({
    action: "CHECK_PERMISSION",
    payload: { permission: perm },
  });
};

window.permissionsAsk = async function (permission = "LOCATION") {
  const perm = String(permission || "LOCATION").toUpperCase();
  return permissionsCall({
    action: "ASK_FOR_PERMISSION",
    payload: { permission: perm },
  });
};

window.permissionsOpenSettings = async function () {
  return permissionsCall({ action: "OPEN_APP_SETTINGS", payload: null });
};

/** CHECK → ASK if needed; DENIED_ALWAYS → open settings. Uses helpers above only. */
window.ensurePermission = async function (permission = "LOCATION") {
  const perm = String(permission || "LOCATION").toUpperCase();

  const check = await window.permissionsCheck(perm);
  if (!check || typeof check.status !== "boolean") {
    return { ok: false, reason: "no_response", detail: check };
  }
  if (!check.status) return { ok: false, step: "check", response: check };

  const s1 = check.params && check.params.permissionStatus;
  if (s1 === "GRANTED") return { ok: true, permissionStatus: s1 };

  if (s1 === "DENIED_ALWAYS") {
    await window.permissionsOpenSettings();
    return { ok: false, permissionStatus: s1, openedSettings: true };
  }

  if (s1 === "NOT_GRANTED" || s1 === "NOT_DETERMINED" || s1 === "DENIED") {
    const ask = await window.permissionsAsk(perm);
    if (!ask || typeof ask.status !== "boolean") {
      return { ok: false, reason: "no_response", detail: ask };
    }
    if (!ask.status) return { ok: false, step: "ask", response: ask };

    const s2 = ask.params && ask.params.permissionStatus;
    if (s2 === "DENIED_ALWAYS") {
      await window.permissionsOpenSettings();
      return { ok: false, permissionStatus: s2, openedSettings: true };
    }
    return { ok: s2 === "GRANTED", permissionStatus: s2 };
  }

  return { ok: false, permissionStatus: s1 };
};

console.log("Permissions bridge ready");

Try it

Run after the block above (so permissionsCall / permissionsCheck exist).

await window.permissionsCheck("LOCATION");

await window.ensurePermission("LOCATION");
// same as ensurePermission("location") — normalized to uppercase

If you see no_response or a missing status: the host did not invoke the third-argument callback, or the permissions bridge is not registered. Your WebInto build must support Native.call(method, payload, callback) (same shape as in-app templates).


Actions

NONE

Confirms the permissions bridge is reachable.

Request

{ "action": "NONE", "payload": null }

Response

{ "status": true }

CHECK_PERMISSION

Reads the current decision without showing a system dialog. On Android, the result may include NOT_GRANTED—see Response shape.

Request (example)

{ "action": "CHECK_PERMISSION", "payload": { "permission": "LOCATION" } }

Response (example)

{
  "status": true,
  "params": {
    "permissionStatus": "GRANTED"
  }
}

Missing or unknown permission

{
  "status": false,
  "message": "please send permission in payload: NOTIFICATION, LOCATION, AUDIO, or VIDEO."
}

ASK_FOR_PERMISSION

Asks the system to show the permission prompt (when the OS allows it). Same payload shape as CHECK_PERMISSION.

After the user responds (or dismisses the sheet), you still usually get status: true with params.permissionStatus. See “How ASK_FOR_PERMISSION maps” under Response shape for the full list (GRANTED, DENIED, DENIED_ALWAYS, NOT_DETERMINED, and when status: false applies).


OPEN_APP_SETTINGS

Opens the app’s system settings screen so the user can toggle permissions manually.

Request

{ "action": "OPEN_APP_SETTINGS", "payload": null }

Response

{ "status": true }

Pair OPEN_APP_SETTINGS with clear UI (“Notifications are blocked—open Settings to enable”) when permissionStatus is DENIED_ALWAYS. Prefer opening settings after the user taps a button, not automatically on every load. For DENIED (without “always”), you may still show in-app guidance before sending them to Settings.


Invalid body

If the JSON cannot be parsed or action is not one of the supported values:

{
  "status": false,
  "message": "please send valid parameters see docs."
}

Tip: only run this inside WebInto.app

If the same URL opens in a normal browser, window.Native.call will be missing. Combine permissionsCall with User-Agent or feature detection (see the OneSignal article) so you do not treat “no bridge” as a permission denial.


Summary

Use Native.call('permissions', { action, payload }, callback) and parse the callback argument (string or object). Follow CHECK → ASK → settings; honor NOT_GRANTED on Android as “needs user action” before assuming a final state. Use window.ensurePermission (or the smaller helpers) from the example code block; gate OPEN_APP_SETTINGS on a button when DENIED_ALWAYS.

For reload, error screens, or connectivity flows, use the web bridge as in the custom HTML no-internet screen guide. For brightness, see the screen brightness tutorial.

Web2App app icon

Ready to start?

Convert your website into an Android app

Install Web2App and build your APK in minutes. No coding required.

Get it on Google Play