# QR and barcode scanner - JavaScript bridge API

> Google’s scanner screen from one promise, AppMint.scanCode() - no camera permission, and the parsed URL, Wi-Fi, phone or contact for common QR codes.

- **Applies to:** Appwright
- **Source:** extracted from the app runtime and its per-method example files; this page is generated from them.
- **HTML:** https://freewebsitetoapk.com/docs/api/scanner

### `AppMint.scanCode`

```js
AppMint.scanCode(options)
```

**Example**

Opens Google's QR code and barcode scanner screen and gives you what was scanned. One call - no camera permission, no `<video>`, no UI of your own.

**Returns:** a Promise that always resolves: `{ok: true, value, displayValue, format, type, …}` or `{ok: false, code, message}`. **Needs:** nothing to switch on. The phone needs Google Play services.

**Any code:**

```js
async function scan() {
  if (!(window.AppMint && AppMint.scanCode)) { alert('Open this page in the app to scan'); return; }
  var r = await AppMint.scanCode();
  if (!r.ok) {
    if (r.code === 'cancelled') return;                          // the user closed the scanner
    if (r.code === 'unavailable') alert('The scanner is not available on this phone');
    else if (r.code === 'busy') alert('A scan is already open');
    else alert('Scan failed: ' + r.message);                     // 'failed'
    return;
  }
  console.log(r.value, r.format, r.type);   // "TICKET-4821", "qr_code", "text"
}
```

**Only some formats**, and a "type it in" option for damaged labels:

```js
var r = await AppMint.scanCode({
  formats: ['ean_13', 'ean_8', 'upc_a', 'upc_e'],   // shop barcodes
  manualInput: true,                                // lets the user type the code
  autoZoom: true                                    // default true
});
if (r.ok) lookUpProduct(r.value);
```

**Ready-made parts** - only the part that matches `r.type` is present:

```js
var r = await AppMint.scanCode({ formats: ['qr_code'] });
if (r.ok) {
  if (r.type === 'url')     openLink(r.url.url);                      // url: { url, title }
  if (r.type === 'wifi')    showWifi(r.wifi.ssid, r.wifi.password, r.wifi.encryption); // 'wpa'|'wep'|'open'
  if (r.type === 'phone')   callNumber(r.phone);                      // a string
  if (r.type === 'email')   writeMail(r.email.address, r.email.subject, r.email.body);
  if (r.type === 'sms')     writeSms(r.sms.phoneNumber, r.sms.message);
  if (r.type === 'geo')     showMap(r.geo.lat, r.geo.lng);
  if (r.type === 'contact') addContact(r.contact.name, r.contact.organization, r.contact.phones, r.contact.emails);
}
```

**Notes:** Format names: `qr_code`, `aztec`, `codabar`, `code_39`, `code_93`, `code_128`, `data_matrix`, `ean_8`, `ean_13`, `itf`, `pdf417`, `upc_a`, `upc_e` (unknown names are ignored; none = all formats). `type` is one of `url`, `wifi`, `text`, `phone`, `email`, `sms`, `geo`, `contact`, `calendar`, `isbn`, `product`, `driver_license`, `unknown`. Error codes: `cancelled` (closed, including with Back), `unavailable` (no Play services, or the scanner could not be downloaded), `busy` (a scan is already open), `failed`. The first scan on a phone can take a few seconds while Play services downloads the scanner. AI-built apps use `scanCode()` from `@/lib/appmintNative`, which also scans on the web.

### `__ocrAvailable`

```js
window.WebToApk.__ocrAvailable(): Boolean
```

TextDetector / recognizeText(): Google Play services is here (the OCR module's home).

**Example**

Internal transport behind `TextDetector`: whether Google Play services (where the recognizer comes from) is on this phone. Pages use the standard API; never call this directly - the `__` methods may change without notice.

**Returns:** `true` or `false`. When `false`, `TextDetector` is not installed at all. **Needs:** nothing.

**Use the public API:**

```js
if ('TextDetector' in window) scanButton.hidden = false;
```

Choose between the scan flow and a typed form once, at start-up. When the phone has no Google Play services the detector is simply absent, so the page must offer another way in:

```js
function setUpReceiptEntry() {
  var canScan = typeof window.TextDetector === 'function';
  document.getElementById('scan-receipt').hidden = !canScan;
  document.getElementById('type-receipt').hidden = canScan;
  if (!canScan) document.getElementById('entry-hint').textContent =
    'Text scanning is not available on this phone. Type the total below.';
}
document.addEventListener('DOMContentLoaded', setUpReceiptEntry);
```

A detector that exists can still answer that recognition is unavailable at the moment of the call. Tell the two failures apart by the error name:

```js
async function readLabel(img) {
  if (typeof window.TextDetector !== 'function') return null;
  try {
    var lines = await new TextDetector().detect(img);
    return lines.map(function (l) { return l.rawValue; }).join('\n');
  } catch (e) {
    if (e.name === 'NotSupportedError') showMessage('Text recognition is not available right now.');
    else showMessage('Could not read the label: ' + e.message);
    return null;
  }
}
```
**Notes:** Implemented in the app shell (TextRecognitionBridge.kt).

### `__ocrRecognize`

```js
window.WebToApk.__ocrRecognize(requestId: String, base64: String)
```

Recognises the text in [base64] (PNG/JPEG); the answer arrives as `appmint:ocr {requestId, blocks|error}`.

**Example**

Internal transport behind `TextDetector.detect()`: recognises the text in a PNG/JPEG. Pages use the standard API; never call this directly - the `__` methods may change without notice.

**Returns:** nothing; the answer arrives as the `appmint:ocr` event `{requestId, blocks:[{text, box, corners, lines}]}` or `{requestId, error}` (`unavailable`, `bad-image`, `failed: …`). **Needs:** nothing.

**Use the public API:**

```js
const lines = await new TextDetector().detect(document.getElementById('receipt'));
console.log(lines.map((l) => l.rawValue));
```

Read the text of a photo the user picks. `detect()` accepts a `Blob`/`File` as well as an `<img>`, `<canvas>` or `ImageData`:

```html
<input type="file" id="doc" accept="image/*" capture="environment">
<pre id="out"></pre>
<script>
document.getElementById('doc').addEventListener('change', async function (e) {
  var file = e.target.files[0];
  if (!file || typeof window.TextDetector !== 'function') return;
  try {
    var lines = await new TextDetector().detect(file);
    document.getElementById('out').textContent = lines.map(function (l) { return l.rawValue; }).join('\n');
  } catch (err) {
    document.getElementById('out').textContent = 'Could not read the photo (' + err.name + ')';
  }
});
</script>
```

Outline each recognised line on top of the image. `boundingBox` is in the image's own pixels, so scale it to the displayed size. The image must have finished loading, or `detect()` rejects with `InvalidStateError`:

```js
async function highlightLines(img, overlay) {
  if (!img.complete) await new Promise(function (r) { img.onload = r; });
  var lines = await new TextDetector().detect(img);
  var sx = img.clientWidth / img.naturalWidth, sy = img.clientHeight / img.naturalHeight;
  overlay.innerHTML = '';
  lines.forEach(function (l) {
    var b = l.boundingBox, box = document.createElement('div');
    box.className = 'ocr-box';
    box.title = l.rawValue;
    box.style.cssText = 'position:absolute;left:' + b.x * sx + 'px;top:' + b.y * sy +
      'px;width:' + b.width * sx + 'px;height:' + b.height * sy + 'px';
    overlay.appendChild(box);
  });
}
```

Pull an amount out of a receipt. A cross-origin image that taints the canvas rejects with `SecurityError`, so serve the picture from the app's own origin:

```js
async function findTotal(img) {
  try {
    var lines = await new TextDetector().detect(img);
    var hit = lines.find(function (l) { return /total/i.test(l.rawValue); });
    var m = hit && hit.rawValue.match(/(\d+[.,]\d{2})/);
    return m ? parseFloat(m[1].replace(',', '.')) : null;
  } catch (e) {
    if (e.name === 'SecurityError') console.warn('Image is from another origin; load it from the app instead.');
    else console.warn('OCR failed:', e.name, e.message);
    return null;
  }
}
```
**Notes:** Implemented in the app shell (TextRecognitionBridge.kt).

### `scanCode`

```js
window.WebToApk.scanCode(optionsJson: String, callbackId: String)
```

Opens Google's code scanner; [optionsJson] `{formats?, autoZoom?, manualInput?}`. Resolves the AppMint.scanCode promise [callbackId].

**Example**

Internal plumbing behind `AppMint.scanCode()` - pages use that Promise helper, which opens Google's QR/barcode scanner and resolves with the result.

**Returns:** nothing directly; the result resolves the `AppMint.scanCode()` promise. **Needs:** nothing to switch on (no camera permission). The phone needs Google Play services.

The public API:

```js
async function scanTicket() {
  if (!(window.AppMint && AppMint.scanCode)) { alert('Open this page in the app to scan'); return; }
  var r = await AppMint.scanCode({ formats: ['qr_code'] });
  if (r.ok) checkTicket(r.value);                  // r.format, r.type, r.displayValue…
  else if (r.code !== 'cancelled') alert('Scan failed (' + r.code + ')');
}
```

Join a Wi-Fi network from a printed QR code. A Wi-Fi code comes back with `type: 'wifi'` and a `wifi` object:

```js
async function scanWifi() {
  if (!(window.AppMint && AppMint.scanCode)) return;
  var r = await AppMint.scanCode({ formats: ['qr_code'] });
  if (!r.ok) { if (r.code !== 'cancelled') alert('Scan failed (' + r.code + ')'); return; }
  if (r.type !== 'wifi') { alert('That is not a Wi-Fi code.'); return; }
  showWifiCard(r.wifi.ssid, r.wifi.password, r.wifi.encryption);   // encryption: 'wpa' | 'wep' | 'open'
}
```

Look up a product by its shop barcode, letting the user type the digits when the label is damaged (`manualInput`). The first scan on a phone may download the scanner, which fails with `unavailable` when offline:

```js
async function scanProduct() {
  if (!(window.AppMint && AppMint.scanCode)) { showManualEntry(); return; }
  var r = await AppMint.scanCode({ formats: ['ean_13', 'ean_8', 'upc_a', 'upc_e'], manualInput: true });
  if (r.ok) return lookUpProduct(r.value, r.format);            // e.g. '4006381333931', 'ean_13'
  if (r.code === 'busy') return;                                  // a scan is already open
  if (r.code === 'unavailable') alert('The scanner is not available: ' + r.message);
  else if (r.code !== 'cancelled') alert('Scan failed (' + r.code + ')');
}
```

Open whatever a QR code points at, by its decoded `type`:

```js
async function scanAndOpen() {
  var r = await AppMint.scanCode({ formats: ['qr_code'], autoZoom: true });
  if (!r.ok) return;
  if (r.type === 'url') location.href = r.url.url;
  else if (r.type === 'phone') location.href = 'tel:' + r.phone;
  else if (r.type === 'email') location.href = 'mailto:' + r.email.address;
  else if (r.type === 'geo') showOnMap(r.geo.lat, r.geo.lng);
  else if (r.type === 'contact') saveContact(r.contact.name, r.contact.phones, r.contact.emails);
  else showText(r.displayValue);
}
```
**Notes:** The raw method takes `optionsJson` (`{formats?, autoZoom?, manualInput?}` as a JSON string) and a `callbackId`; the answer is delivered to the helper's internal reply table, so a page calling it directly gets no answer. Only one scan runs at a time - a second call answers `code: 'busy'`. See `AppMint.scanCode` for every result field and error code.

