# GeoVerdict autocomplete widget v1

Dependency-free address autocomplete for browser checkout and signup forms. This directory is versioned so v1 integrations can remain stable while later major versions evolve independently.

> The widget deliberately rejects `ak_live_…`, `ak_test_…`, and `gv_sess_…` credentials. It requires an origin-restricted **publishable token** issued for one project. Never put a GeoVerdict API key in browser code.

## Install

The shortest setup is an input and one script tag. The script loads the versioned widget CSS, infers the GeoVerdict endpoint, and mounts itself from the `data-*` attributes.

```html
<label for="address-search">Find your address</label>
<input id="address-search" type="text">
<script src="https://geoverdict.com/widget/v1/geoverdict-autocomplete.js"
  data-input="#address-search"
  data-token="gv_pk_live_REPLACE_WITH_PUBLISHABLE_TOKEN"
  data-country="NL" defer></script>
```

For a larger configuration, set one global before loading the script. No mount function call is needed.

```html
<input id="address-search" type="text">

<input id="street" name="street">
<input id="house-number" name="houseNumber">
<input id="postcode" name="postcode">
<input id="city" name="city">
<input id="country" name="country">

<script>
  window.GeoVerdictAutocompleteConfig = {
    input: '#address-search',
    token: 'gv_pk_live_REPLACE_WITH_PUBLISHABLE_TOKEN',
    country: 'NL',
    language: 'en',
    fields: {
      street: '#street',
      houseNumber: '#house-number',
      postcode: '#postcode',
      city: '#city',
      country: '#country'
    },
    cssVariables: {
      '--gv-color-accent': '#2563eb',
      '--gv-radius': '8px'
    }
  };
</script>
<script src="https://geoverdict.com/widget/v1/geoverdict-autocomplete.js" defer></script>

<script>
  document.querySelector('#address-search').addEventListener('geoverdict:select', (event) => {
    console.log(event.detail.address);
  });
</script>
```

The original programmatic API remains available for frameworks and lifecycle control:

```js
const addressWidget = GeoVerdictAutocomplete.mount({
  input: '#address-search',
  token: 'gv_pk_live_REPLACE_WITH_PUBLISHABLE_TOKEN'
});
```

An element can be passed anywhere a selector is accepted. Keep the returned instance and call `destroy()` before a client-side framework removes the input. The `endpoint` defaults to `https://geoverdict.com/v1/widget/autocomplete` when the script is loaded from GeoVerdict.

## Options

| Option | Required | Default | Description |
|---|---:|---|---|
| `input` | yes | — | Input element or selector. |
| `endpoint` | no | inferred from script | HTTPS publishable-widget endpoint. Set it explicitly when self-hosting the JavaScript. HTTP is accepted only on localhost. |
| `token` | yes | — | Origin-restricted publishable token. Secret API keys and console sessions are rejected. |
| `country` | no | project policy | ISO 3166-1 alpha-2 country filter. Omit for global search. |
| `language` | no | project policy | BCP 47 response language, such as `en` or `nl-NL`. |
| `fields` | no | `{}` | Selectors/elements keyed by `label`, `street`, `houseNumber`, `postcode`, `city`, `state`, or `country`. Values are filled after selection and emit native `input` and `change` events. |
| `limit` | no | `5` | 1–10 suggestions. |
| `minLength` | no | `3` | Characters required before a request. |
| `debounceMs` | no | `250` | Input debounce, 0–2000 ms. Superseded requests are cancelled. |
| `source` | no | `widget` | Usage source tag, up to 64 characters. |
| `manualEntry` | no | enabled | `false` to hide the fallback, or `{ label, searchLabel }` to customize it. |
| `resultsLabel` | no | `Address suggestions` | Accessible listbox name. |
| `browserAutocomplete` | no | `off` | Value for the browser's native `autocomplete` attribute. |
| `cssVariables` | no | `{}` | CSS custom properties whose names start with `--gv-`. |
| `onSelect` / `onError` | no | — | Callback alternatives to the DOM events below. |

Public instance methods: `search(query)`, `clear()`, `enable()`, `disable()`, `setManual(boolean)`, and `destroy()`.

## Events

Events bubble from the configured input.

- `geoverdict:select`: `{ suggestion, address, sessionToken }`
- `geoverdict:error`: `{ code, message, status? }`; codes are `authentication_failed`, `rate_limited`, `request_failed`, `network_error`, or `invalid_response`
- `geoverdict:manual`: `{ enabled }`

The widget uses a combobox/listbox pattern, a polite live region, keyboard navigation (`ArrowUp`, `ArrowDown`, `Home`, `End`, `Enter`, `Escape`), and safe DOM construction. Provider data is assigned with `textContent`; it is never inserted as HTML.

## Backend contract

The browser endpoint is intentionally **not** the API-key-authenticated `/v1/autocomplete` route. Create an origin-restricted publishable token from the project's **Autocomplete widget** panel in the GeoVerdict console, then pass that token to the widget as shown above.

### Request

```http
POST /v1/widget/autocomplete
Authorization: Bearer gv_pk_live_…
Content-Type: application/json
X-GeoVerdict-Widget-Version: 1.1.0
Origin: https://merchant.example
```

```json
{
  "query": "Prinsengracht 26",
  "country": "NL",
  "language": "en",
  "limit": 5,
  "sessionToken": "client-generated-session-id",
  "source": "widget"
}
```

`country` and `language` are omitted when not configured. The server must validate them and may enforce stricter project defaults.

### Response

```json
{
  "suggestions": [
    {
      "id": "provider-safe-opaque-id",
      "label": "Prinsengracht 263, 1016 GV Amsterdam, Netherlands",
      "provider": "bag",
      "address": {
        "street": "Prinsengracht",
        "houseNumber": "263",
        "postcode": "1016 GV",
        "city": "Amsterdam",
        "state": "Noord-Holland",
        "country": "NL"
      }
    }
  ],
  "sessionToken": "client-generated-session-id",
  "cached": false
}
```

The widget ignores unknown response fields and drops malformed suggestions.

### Security requirements

The GeoVerdict widget endpoint:

1. Authenticate only a separately stored, hashed publishable-token type (for example `gv_pk_live_…`), never accept `ak_…` or `gv_sess_…` credentials.
2. Scope each token to one active project and autocomplete only. Token rotation/revocation must not affect server API keys.
3. Compare the request `Origin` against the project's exact HTTPS origin allowlist. Do not rely on CORS alone for authorization; reject missing/disallowed origins before calling providers.
4. Return `Access-Control-Allow-Origin` for the validated origin (never `*` with a token), `Vary: Origin`, and allow `Authorization`, `Content-Type`, and `X-GeoVerdict-Widget-Version` in preflight responses.
5. Apply per-token and per-origin rate limits, query/field limits, credit gates, provider routing, and usage logging server-side. Client options are untrusted.
6. Avoid returning provider-native identifiers or metadata unless their terms allow browser exposure. Use opaque IDs when IDs are needed.
7. Never log the bearer token or return it in an error body.

Autocomplete usage is currently charged per provider call. `sessionToken` correlates keystrokes within a browser session but does not currently deduplicate billing. If you proxy autocomplete through your own backend instead, use the server-side `/v1/autocomplete` API and keep the `ak_…` key there; never adapt this widget to accept it.

## Styling

The shipped CSS honors:

`--gv-color-accent`, `--gv-color-accent-soft`, `--gv-color-text`, `--gv-color-muted`, `--gv-color-surface`, `--gv-color-border`, `--gv-radius`, `--gv-shadow`, `--gv-font-family`, and `--gv-z-index`.

Override them in `cssVariables` or on `.gv-autocomplete`. By default the script adds a `<link>` for the CSS beside the JavaScript bundle, so it does not require inline styles and works with a `style-src` policy that allows `https://geoverdict.com`.

For self-hosted or custom CSP setups, add `data-styles="false"` to the script and include the stylesheet yourself. `data-stylesheet="https://…"` can point the automatic loader at a custom CSS URL.
