ENGARDE

Для разработчиков

window.engarde

Когда HTML-страница открывается через desktop-клиент Engarde в overlay-зоне (например .engarde / .rus), клиент внедряет в <head> мост:

html
<script src="/__engarde/bridge.js"></script>

Скрипт выставляет window.engarde до запуска модулей страницы. Вне Engarde объекта нет — всегда делайте feature-detect.

js
if (!window.engarde) {
  // обычный браузер — нет локального SQLite / моста
}
await window.engarde.ready;
flowchart LR
  Page["JS overlay-страницы"] -->|вызов| Bridge["window.engarde"]
  Bridge -->|WebSocket| WS["/__engarde/ws"]
  WS --> Client["Engarde desktop"]
  Client --> SQL["SQLite на origin"]

Доступность

Контекст window.engarde
Overlay-сайт через Engarde SOCKS / DNS Есть, __engardeNative === true
Тот же URL в Chrome / Safari без Engarde Нет
Vite npm run dev без клиента Нет

Перед вызовами дождитесь ready — промис резолвится, когда открыт WebSocket к /__engarde/ws.

Поверхность API (v0.1)

ts
type EngardeBridge = {
  version: string;           // например "0.1"
  ready: Promise<void>;
  __engardeNative: true;
  sql: {
    query(sql: string, args?: unknown[]): Promise<SqlQueryResult>;
    exec(sql: string, args?: unknown[]): Promise<SqlExecResult>;
  };
  doc: { get(): Promise<unknown>; set(): Promise<unknown> }; // reserved
  rpc: { connect(): Promise<unknown>; call(): Promise<unknown> }; // reserved
};

type SqlQueryResult = {
  columns: string[];
  rows: unknown[][];   // строка = массив по columns
};

type SqlExecResult = {
  changes: number;
  lastInsertRowid: number | null;
};

Уже поддерживается

На native-стороне реализованы только sql.query и sql.exec.

Зарезервировано (пока не реализовано)

  • doc.* — sync документа / CRDT (ответ not_implemented)
  • rpc.* — peer RPC (ответ not_implemented)

Вызовы отклоняются с ошибкой, у которой code === "not_implemented".

SQL API

Хранилище на origin overlay-сайта (hostname): файл SQLite в data-директории клиента (overlay_site_db/…). Разные сайты таблицы не делят.

sql.exec(sql, args?)

Оператор без result set (DDL / INSERT / UPDATE / DELETE).

js
await window.engarde.ready;
await window.engarde.sql.exec(`
  CREATE TABLE IF NOT EXISTS kv (
    key TEXT PRIMARY KEY,
    value TEXT NOT NULL
  )
`);
await window.engarde.sql.exec(
  `INSERT INTO kv (key, value) VALUES (?1, ?2)
   ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
  ['counter', '3'],
);

Возвращает { changes, lastInsertRowid }.

sql.query(sql, args?)

Чтение (SELECT). Возвращает { columns, rows }, где каждая строка — массив значений ячеек.

js
const { columns, rows } = await window.engarde.sql.query(
  'SELECT value FROM kv WHERE key = ?1',
  ['counter'],
);
const value = rows[0]?.[0]; // первая колонка первой строки

Параметры

Нумерованные плейсхолдеры SQLite (?1, ?2, …). Значения — JSON-сериализуемые: строки, числа, boolean, null.

Лимиты (на стороне клиента)

Лимит Значение
Текст SQL 64 KiB
Аргументы 64
Размер строкового аргумента 256 KiB каждый
Строк в результате 10 000
Размер ответа ~1 MiB
Файл БД 64 MiB
Таймаут statement 5 с
Rate limit ~30 req/s на host (burst 60)
Одновременных WS 8 на host

Ошибки

Неудачный вызов отклоняет Promise с Error и опциональным error.code:

Code Смысл
not_implemented doc / rpc (или неизвестный метод)
invalid_params Плохой SQL / args
forbidden Rate limit / слишком много сокетов
timeout Statement дольше 5 с
quota_exceeded Слишком много строк / большой результат
sql_error Ошибка SQLite / worker
unauthorized Мост отклонил host

Формат по проводу (для отладки): JSON по WebSocket { id, ns, method, params } → { id, ok, result } или { id, ok: false, error: { code, message } }.

Минимальный пример

js
async function loadCounter() {
  const api = window.engarde;
  if (!api) return { mode: 'memory', value: 0 };

  await api.ready;
  await api.sql.exec(
    'CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)',
  );
  const { rows } = await api.sql.query(
    'SELECT value FROM kv WHERE key = ?1',
    ['counter'],
  );
  return { mode: 'saved', value: Number(rows[0]?.[0] ?? 0) || 0 };
}

Публикация сайта

  1. Собрать статику (dist/).
  2. Опубликовать через engarde-operator site run dist (пишет engarde.json, сидит Collection).
  3. Посетители открывают overlay-hostname через Engarde; HTML получает мост автоматически.

См. также пример в монорепо: examples/overlay-demo.