# Events and errors

The DOM events the element fires and the error codes onestar:error carries.

The `<onestar-feedback>` element fires DOM events as the form loads, opens, is answered and closes, and `onestar:error` when something stops it. Each event's `detail` names the form and never anything about the person answering.

## Listening

Events bubble and cross the shadow root, so you can listen on the element or on `window`:

```js
const form = document.querySelector("onestar-feedback");
form.addEventListener("onestar:submit", (e) => {
  if (e.detail.final) console.log("Finished", e.detail.form);
});

window.addEventListener("onestar:error", (e) => {
  console.warn(e.detail.code, e.detail.message);
});
```

Listen before the form loads (anywhere before or right after the element), so you do not miss `onestar:ready` or an early `onestar:error`. For React, Vue and Svelte, see [Frameworks](https://onestar.sh/docs/feedback/frameworks.md).

## Events

| Event | Fires when | `detail` |
|---|---|---|
| `onestar:ready` | The form loaded and drew for the first time, including a closed form showing its closed message. | `{ form }` |
| `onestar:open` | A popover, slide-in or modal opened. | `{ form }` |
| `onestar:answer` | The person picked or typed an answer. It fires on every change, so on each keystroke in a written answer and each move of a slider. | `{ form, block }` |
| `onestar:submit` | Answers were saved. The widget saves a step at a time, so this fires for each step; `final` is true only when the person finished the form. | `{ form, final }` |
| `onestar:close` | An overlay closed. | `{ form }` |
| `onestar:replied` | A reply to this person's earlier answer is waiting. | `{ form, unread }` |
| `onestar:reply` | The person answered back in the conversation view inside the form. | `{ form }` |
| `onestar:error` | The form could not show, or an answer could not be saved. | `{ form, code, message, origin }` |
| `onestar:posted` | On a [feature board](https://onestar.sh/docs/feedback/boards.md), an idea or comment was posted. `state` is `held` while it waits to be checked, else `live`. | `{ form, id, state }` |
| `onestar:voted` | On a feature board, a vote was cast or taken back. `votes` is the idea's count now. | `{ form, id, voted, votes }` |

Notes on a few of them:

- `block` on `onestar:answer` is the question id, the same id the form builder shows.
- `onestar:submit` from `onestar.submit()` (your own UI) is fired on `window` only, since there is no element.
- `onestar:replied` can fire twice for one form: once from the unread check on an overlay's button, and once when the form loads and shows "The makers replied". `unread` may be 0 on the second when the replies were already read.
- `onestar:reply` fires from the conversation view inside a form. `mode="thread"` and `<onestar-inbox>` do not fire it.
- `origin` is present only on `onestar:error` with `origin_refused`.
- `message` is one plain sentence you may show or log.

## Error codes

`onestar:error` carries one of these codes. Codes OneStar adds later pass through as they are, so treat an unknown code like `server`.

| Code | When | What to do |
|---|---|---|
| `origin_refused` | The page's site is not in the form's allowed sites, on load (with `origin`) or when an answer is sent. The form hides itself; an overlay closes once opened and the popover's tab goes. | Add the origin under **Install**, **Allowed sites**. The console names it. |
| `form_closed` | The form is closed, paused, past its closing date or answer limit, not published, or the id is unknown. The form shows its closed message. | Publish or reopen the form, or check the id on its **Install** tab. |
| `invalid_form` | The `form` attribute is not a form id: `f_` and 16 lower case letters and digits. | Copy the id from the form's **Install** tab. |
| `network` | The OneStar API did not answer: offline, a `connect-src` without `https://api.onestar.sh`, or a blocked request. The form offers **Try again**, or **Retry** for an answer. | Check the connection and your Content Security Policy ([Allowed sites and security](https://onestar.sh/docs/feedback/security.md)). |
| `load_failed` | The widget code could not load: a `script-src` without `https://embed.onestar.sh`, an ad blocker, a proxy. The next open or scroll into view tries again. | Allow `https://embed.onestar.sh` in `script-src`. |
| `identity_required` | The form is for signed in users of your product and no token is set, or the token was refused. Fired once each time the form starts asking for a sign in, and the form shows its sign in message. | Call `onestar.identify(token)` or set `user-token` ([Signed in users](https://onestar.sh/docs/feedback/identify-users.md)). |
| `stale_form` | The form stayed open so long that the token it loaded with expired. The person gets a **Reload** button. | Nothing; reloading fixes it. |
| `version_changed` | You published a new version while the person was answering. They get a **Reload** button. | Nothing; reloading shows the new questions. |
| `too_fast` | The answer came sooner after loading than a person could send it. The widget waits a moment and sends it once more before it reports this. | Nothing for a person; a script that sends at once is refused. |
| `not_found` | A later step could no longer be added to the answer, because too long passed since its first step. The person gets a **Reload** button. | Nothing; the answer saved so far stays. |
| `rate_limited` | Too many requests from this network address, or to this form. The person gets **Retry**. | Wait and try again. |
| `server` | A failure on OneStar's side. The widget sends the identical answer once more on its own, then shows **Retry**. | Try again; a retry never counts twice. |

These are not errors and fire nothing, because the form shows them to the person itself:

- An answer the person can fix, such as a missing required answer or a badly typed email. The form shows the message beside the question.
- A second answer to a form that allows one answer per person. The form shows "You already answered this. Thanks."

`onestar.submit()` rejects with the same codes, plus `closed`, `invalid_answer` and `already_answered`; see [Your own UI](https://onestar.sh/docs/feedback/headless.md).

## Console messages

Some problems only you can fix, so the widget also writes one line to the browser console:

| Message | Means |
|---|---|
| `OneStar: form <id> does not allow <origin>. Add <origin> under Install, Allowed sites in OneStar.` | The site is not on the form's allowed list. |
| `OneStar: form <id> could not load: the OneStar API did not answer. Check the connection, that connect-src allows https://api.onestar.sh, and that <origin> is in the form's allowed sites.` | The API could not be reached. |
| `OneStar: form <id> was not found or is not live. Check the id on the form's Install tab.` | The id is wrong, or the form was deleted or never published. |
| `OneStar: "<value>" is not a form id. Copy it from the form's Install tab.` | The `form` attribute is not shaped like a form id. |
| `OneStar: the feedback renderer could not load from <url>. Likely causes: a Content Security Policy whose script-src does not allow https://embed.onestar.sh, or a blocked request (an ad blocker, a proxy or firewall, no network).` | The widget code was blocked. |

For a form for signed in users with no token, the `onestar:error` message says: "This form is for signed in users and no user token is set. Call onestar.identify(token) or set user-token."

## TypeScript

To type the events on elements and on `window`, add this to a `.d.ts` file:

```ts onestar-events.d.ts
type OneStarErrorCode =
  | "origin_refused" | "form_closed" | "invalid_form" | "network" | "load_failed" | "identity_required"
  | "stale_form" | "version_changed" | "too_fast" | "not_found" | "rate_limited" | "server"
  | (string & {});

interface OneStarEventMap {
  "onestar:ready": CustomEvent<{ form: string }>;
  "onestar:open": CustomEvent<{ form: string }>;
  "onestar:answer": CustomEvent<{ form: string; block: string }>;
  "onestar:submit": CustomEvent<{ form: string; final: boolean }>;
  "onestar:close": CustomEvent<{ form: string }>;
  "onestar:replied": CustomEvent<{ form: string; unread: number }>;
  "onestar:reply": CustomEvent<{ form: string }>;
  "onestar:error": CustomEvent<{ form: string; code: OneStarErrorCode; message: string; origin?: string }>;
}

declare global {
  interface HTMLElementEventMap extends OneStarEventMap {}
  interface WindowEventMap extends OneStarEventMap {}
  interface HTMLElementTagNameMap {
    "onestar-feedback": HTMLElement;
    "onestar-inbox": HTMLElement;
  }
}

export {};
```

With it, `document.querySelector("onestar-feedback")` returns an `HTMLElement`, and `e.detail.final` in an `onestar:submit` listener on it or on `window` is typed.
