# The sg API

> window.sg connects a plugin page to Broadcast: pageData, call, toast, confirm, copy, subscriberId and SgError, with error handling.

Source: https://sendbroadcast.net/docs/signal-sg-api

> Coming in Broadcast 3.0. Until then, this page applies only to the Upcoming (edge) build.

`kit.js` adds one global object, `sg`. It is how your page talks to Broadcast and to the person using it.

| Name | Does |
|---|---|
| [`sg.pageData(input, page)`](#sg-pagedata) | Calls your plugin's `pageData` function |
| [`sg.call(op, args)`](#sg-call) | Calls one bridge operation |
| [`sg.toast(text, options)`](#sg-toast) | Shows a short message |
| [`sg.confirm(options)`](#sg-confirm) | Asks yes or no |
| [`sg.copy(text)`](#sg-copy) | Copies text |
| [`sg.openExternal(url)`](#sg-openexternal) | Opens a link outside Broadcast, after the person agrees |
| [`sg.subscriberId()`](#sg-subscriberid) | Reads the subscriber of a panel |
| `sg.SgError` | The error class |
| `sg.version` | `"1"` |

## sg.pageData

Calls `pageData` in your `dist/main.js`, and gives you what it returns.

```javascript
const data = await sg.pageData({ action: "refresh" });
```

| Value | Default | Meaning |
|---|---|---|
| `input` | `{}` | Any JSON object. Your function gets it as `input` |
| `page` | `0` | A number your function gets as `page`. Use it for a page index, or ignore it |

On the plugin side:

```javascript
export default definePlugin({
  async pageData({ page, input }, ctx) {
    return { message: `You asked for ${input.action}` };
  },
});
```

This is the main door. Use it for everything that needs secrets, writes or `fetch`. `pageData` runs with the plugin's permissions, so check `input`.

## sg.call

Calls one bridge operation directly. It gives the result, or it throws.

```javascript
const opens = await sg.call("store.increment", { key: "opens" });
const tags = await sg.call("tags.list");
const person = await sg.call("store.get", { key: "note", subscriber_id: 12 });
```

[Plugin Pages](https://sendbroadcast.net/docs/plugins-pages#what-a-page-can-call) lists the allowed operations. A page acts as the person viewing it, and it cannot change subscribers or read secrets. For those, use `pageData`.

## SgError

`sg.call` and `sg.pageData` throw an `SgError` when a call fails. It has two useful fields:

| Field | Meaning |
|---|---|
| `code` | A short code, such as `forbidden`, `not_found`, `plugin_error` or `timeout` |
| `message` | Text for you |

```javascript
try {
  await sg.call("subscribers.list");
} catch (error) {
  if (error.code === "forbidden") {
    sg.toast("This plugin may not read subscribers.", { tone: "danger" });
  } else {
    throw error;
  }
}
```

Always handle errors. A call can fail for reasons that are not your bug, such as a slow plugin. Without a `try`, the page just stops, with no word to the person.

When your `pageData` function throws, Broadcast does not pass its message to the browser. Both `code` and `message` are then `plugin_error`. Read the plugin's [logs](https://sendbroadcast.net/docs/plugins-testing) for the real message. To show a friendly message, return it as data instead of throwing:

```javascript
if (!text) return { error: "Write a note first." };
```

## sg.toast

A short message at the bottom right that goes away by itself.

```javascript
sg.toast("Saved");
sg.toast("Not saved: the server did not answer", { tone: "danger", duration: 8000 });
```

| Option | Default | Meaning |
|---|---|---|
| `tone` | Dark | `danger` makes it red. Other tones have no effect |
| `duration` | `4000` | Milliseconds on screen |

It returns the message element. Text is shown as text.

**Common mistake.** Showing important information in a toast. It disappears. Use `sg-banner`.

## sg.confirm

Asks a question in a dialog. It gives `true` or `false`.

```javascript
const yes = await sg.confirm({
  title: "Delete this poll?",
  message: "The answers are deleted too.",
  confirmLabel: "Delete poll",
  danger: true,
});
if (!yes) return;
```

| Option | Default | Meaning |
|---|---|---|
| `title` | `Are you sure?` | The question |
| `message` | | More detail |
| `confirmLabel` | `Confirm` | The button that says yes |
| `cancelLabel` | `Cancel` | The button that says no |
| `danger` | `false` | Makes the yes button red |

The Esc key counts as no. Name the action in `confirmLabel`: "Delete poll" is better than "OK".

## sg.copy

Copies text. It gives `true` if that worked, and `false` if not.

```javascript
const copied = await sg.copy(link);
sg.toast(copied ? "Copied" : "Press Cmd+C to copy");
```

Call it from a click. The sandbox can refuse the clipboard. In a test with no click, it gave `false`. So always check the answer, or use [sg-copy](https://sendbroadcast.net/docs/signal-actions#sg-copy), which handles this for you.

## sg.openExternal

Asks Broadcast to open a link outside Broadcast. Broadcast shows the person the full address, and the person chooses. It gives `{ opened: true }` or `{ opened: false }`.

```javascript
button.addEventListener("click", async () => {
  const { opened } = await sg.openExternal("https://example.com/guide");
});
```

Call it from a click. The address must be plain `https`, with no user name or password, and up to 2048 characters. Otherwise it throws an `SgError` with one of these codes:

| Code | Meaning |
|---|---|
| `invalid_url` | Broadcast does not accept the address |
| `no_user_action` | The call did not come from a recent click inside your page |
| `busy` | The dialog is open, or it closed less than a second ago |

## sg.subscriberId

In a `subscriberPanel` frame, it gives the subscriber's ID as a string. Anywhere else, it gives `null`.

```javascript
const id = sg.subscriberId();
```

## Components and events

All Signal events bubble, so you can listen on a parent. The data is in `event.detail`.

| Event | From | `detail` |
|---|---|---|
| `sg-submit` | `sg-form` | `{ values }` |
| `sg-search` | `sg-search` | `{ value }` |
| `sg-change` | `sg-checkbox`, `sg-switch` | `{ checked }` |
| `sg-change` | `sg-radio-group`, `sg-tag-input` | `{ value }` |
| `sg-select` | `sg-menu` | `{ value }` |
| `sg-close` | `sg-dialog` | `{ value }` |
| `sg-tab` | `sg-tabs` | `{ index, value }` |
| `sg-page` | `sg-pagination` | `{ page }` |
| `sg-row-click` | `sg-table` | `{ row }` |
