# Network and storage (/docs/products/dash-dots/developers/network-and-files)

Your dot talks to your server through `this.dash.network`, and keeps things on the phone with `this.dash.storage`. Your server needs no CORS headers for a dot.

## Allowed hosts

A dot can only reach the hosts listed under `network.hosts` in `dot.json`. A request anywhere else fails with `denied`:

```json title="dot.json"
{
  "network": { "hosts": ["api.yourcompany.sl", "*.cdn.yourcompany.sl"] }
}
```

`*.cdn.yourcompany.sl` covers its subdomains, but not `cdn.yourcompany.sl` itself. If your server redirects, the new host must be listed too.

The list also covers pictures: `<Image src="https://cdn.yourcompany.sl/menu/jollof.jpg" />` shows only when its host is listed. A page can't reach anything else directly, `fetch` included. All your dot's traffic goes through `dash.network`.

### A server on your computer

While you develop, the list can name a server on your own computer or network, such as `localhost` or `192.168.1.20`. That works in `monime dot dev`, the simulator and live tests. `monime dot build` refuses these hosts, and Dash refuses them in published dots. Before you publish, list your server's public name instead.

## Requests

`dash.network.http.request` works a lot like `fetch`. This sends a meter top-up order to your server:

```ts
const response = await this.dash.network.http.request<Order>({
  url: `${env.API_URL}/orders`,
  method: 'POST',
  headers: { 'Idempotency-Key': state.orderKey },
  body: { meter: state.meter, amount: state.amount },
  as: 'json',
});
if (!response.ok) {
  this.setState({ note: "Your order didn't go through. Try again." });
  return;
}
this.setState({ order: response.body });
```

- A 404 or a 500 is still an answer. Check `ok` or `status`. The call only rejects, with `failed`, when no answer arrives. Its message says why, like "Could not reach api.boenergy.sl: the phone is offline".
- An object body goes as JSON, and a string as text. Leave out `as: 'json'` to get the body as text.
- Dash keeps no cookies. Send any token your server needs in a header.

### Send an order once

A person may tap Pay twice, or retry after a dropped connection. Make one key per order with `this.dash.crypto.randomUUID()`, keep it in state, and send it as the `Idempotency-Key` header, as above. Your server then handles each key once, and a retry gets back the first answer. Monime's API works the same way: see [Idempotency](/docs/developer-resources/api-basics/idempotency).

## Online or not

Before a payment, or anything else that needs the network, check `dash.network.status`:

```ts
const { online } = await this.dash.network.status();
if (!online) {
  this.setState({ note: "You're offline. Connect to pay." });
  return;
}
```

Online doesn't mean your server will answer. Still handle a failed request.

## Download and upload files

`download` saves a response to a file, and `upload` sends files as a form:

```ts
const { ok, file } = await this.dash.network.http.download({ url: `${env.API_URL}/receipts/${state.orderId}` });
if (ok) {
  this.setState({ receipt: file });
}

await this.dash.network.http.upload({
  url: `${env.API_URL}/scans`,
  files: [{ field: 'scan', file: state.receipt }],
});
```

### File objects

Downloads, photos and files the person chooses all arrive as a file object, `{ id, name, size, contentType, url }`. Show an image with its `url`, as in `<Image src="{{ receipt.url }}" />`. To upload, share or save it, pass the object itself. It's gone when the dot closes, unless you save it.

## The dot's own files

`dash.storage.file` keeps files on the phone, in a folder only your dot can see. They stay until your dot removes them, or the person clears the dot's data in Dash.

```ts
await this.dash.storage.file.write('meters.json', JSON.stringify(state.meters));
await this.dash.storage.file.save(state.receipt, `receipts/${state.orderId}.pdf`); // Keep a download.
```

Paths are relative (`receipts/42.pdf`), and missing folders are made for you. `save`, `move` and `copy` won't replace a file unless you pass `{ overwrite: true }`.

Reading a path with nothing there rejects with `missing`. On a first visit, that only means nothing's saved yet:

```ts
import { isDashError } from '@monimesl/dot-runtime';

try {
  const saved = await this.dash.storage.file.read('meters.json', { as: 'text' });
  this.setState({ meters: JSON.parse(saved) });
} catch (error) {
  if (!isDashError(error, 'missing')) {
    throw error;
  }
}
```

The [Files reference](/docs/products/dash-dots/reference/dash-api/files) has the rest: `open`, `list`, `remove` and more. In the simulator, the files live in your project's `.dot/files/` folder.

To let the person pick a file from their phone, see [`dash.media.pickFile`](/docs/products/dash-dots/developers/dash-apis#files-the-person-chooses).

## Cache and secrets

The cache holds data you can fetch again, such as this week's okada fares. Secrets hold the person's own tokens, encrypted on the phone. Their session with your server is one.

```ts
await this.dash.storage.cache.set('fares', fares, { ttl: 60 * 60 * 1000 }); // An hour, in milliseconds.
const cached = await this.dash.storage.cache.get('fares'); // null once it's expired.

await this.dash.storage.secret.set('session', token);
```

Your own API keys [stay off the phone](/docs/products/dash-dots/developers/security#keep-secrets-off-the-phone), even in secrets. In the simulator, secrets sit in `.dot/.secrets/` as plain JSON.

> **Storage stays on one phone:** A dot's storage lives on one phone, for whoever is signed in to Dash there. It's never backed up or moved to a new phone. Keep what matters on your server.

## Limits

A later Dash may raise these limits, but won't lower them while the Dash API stays on version 1.x.

| What                                                        | Limit                           |
| ----------------------------------------------------------- | ------------------------------- |
| The dot's files                                             | 256 MB in all                   |
| One `read` of a file                                        | 5 MB                            |
| The cache                                                   | 64 MB in all                    |
| One cache value                                             | 1 MB                            |
| A cache key                                                 | 512 characters                  |
| Secrets                                                     | 32 secrets, up to 4 KB each     |
| A response's body                                           | 5 MB                            |
| A download                                                  | 256 MB                          |
| A file the person chooses                                   | 25 MB                           |
| A photo                                                     | 2048 pixels on its longest side |
| Downloads, photos and chosen files, each time the dot opens | 200 files and 512 MB in all     |
| Network calls at once                                       | 6                               |

Past the limit for downloads, photos and chosen files, more fail with `failed` until the dot opens again. Network calls past 6 wait their turn.
