Эта страница переведена машинным способом и может содержать ошибки. Читать оригинал на английском

API

Стандартные модули

Native Shell предоставляет одиннадцать стандартных модулей для TSX, которые импортируются по голым именам (код на Rust использует собственные библиотеки Rust: Из Rust). Это API в стиле Node, совместимые с API gpui-shell. Они работают в VM приложения на нативной среде выполнения NSH, а их разрешения проверяются в Rust. Объявления TypeScript в .nsh/types генерируются из тех же спецификаций, что и сами модули, поэтому типы показывают именно то, что экспортирует среда выполнения.

МодульЭкспорты
bufferBuffer, atob, btoa, constants, default (LLRT); Buffer также является глобальным
cryptocreateHash, createHmac, randomBytes, randomFill, randomFillSync, randomInt, randomUUID, getRandomValues, webcrypto, crypto, Crc32, Crc32c, Md5, Sha1, Sha256, Sha384, Sha512, default (LLRT); crypto также является глобальным
pathbasename, delimiter, dirname, extname, format, isAbsolute, join, normalize, parse, relative, resolve, sep, default (LLRT)
urlURL, URLSearchParams, domainToASCII, domainToUnicode, fileURLToPath, format, pathToFileURL, urlToHttpOptions, default; URL и URLSearchParams — глобальные классы NSH
zlibdeflate, deflateRaw, inflate, inflateRaw, gzip, gunzip, brotliCompress, brotliDecompress, zstdCompress, zstdDecompress, каждая со своей формой Sync, default (LLRT)
osplatform(), arch(), homedir(), EOL, default
consoledefault: console из VM (log, info, debug, warn, error)
fs/promisesreadFile, writeFile, readdir, exists, mkdir, rename, unlink, rmdir, stat, lstat, readlink, default
processrun, spawn, exit, platform, arch, nextTick, ChildProcess, default; у объекта default также есть env и dataDir, и он является глобальным process
netconnect, Socket, default
websocketconnect(url, options?), возвращающая WebSocketConnection (read, write, close(code?, reason?), closeCode, closeReason), WebSocketConnection, default; глобальный конструктор WebSocket на нативных хостах

#Из Rust

Код на Rust выполняет ту же работу средствами стандартной библиотеки Rust и crates.io, в собственном нативном коде приложения. Блокирующие вызовы выполняются в потоках среды выполнения с blocking(…), а асинхронные — с background(…) (nsh_core::prelude), и их результаты возвращаются в компонент в задаче. capabilities из nsh.json управляют TSX приложения и сервисами NSH (fetch, буфер обмена), а собственный код Rust приложения выполняется с правами процесса приложения. В браузерных приложениях код на Rust обращается к сети через fetch и хранит данные через nsh_core::storage::Storage.

МодульRust
bufferVec<u8>, &[u8], крейт base64
cryptoкрейты, такие как sha2, hmac, rand и uuid
pathstd::path
urlкрейт url
zlibкрейты, такие как flate2, brotli и zstd
osstd::env::consts, os()
consoleкрейт log
fs/promisesstd::fs в blocking(…); Storage для данных «ключ — значение»
processstd::process::Command в blocking(…)
netstd::net::TcpStream в blocking(…) или tokio::net в background(…)
websocketкрейт, такой как tokio-tungstenite, в background(…)

#Импорты и сгенерированные типы

Каждый модуль импортируется ровно под тем именем, которое указано в таблице: import { join } from "path", import fs from "fs/promises". Бандлер сохраняет эти импорты, а среда выполнения обслуживает их нативно; каталог состоит из этих одиннадцати имён. buffer, crypto, path, url и zlib — это реализации из LLRT, зафиксированные на d9fd873e793c6551c5d92b2e31480f3279e3cd8d, с объявлениями, спроецированными из этой ревизии на экспорты, которые есть в среде выполнения. Остальные модули написаны в NSH. NSH сохраняет идентичность своих классов URL, Blob, потоков и событий.

Все одиннадцать модулей загружаются на нативных хостах и в браузерных приложениях. Buffer, хеширование, криптография и сжатие используют одну и ту же переносимую реализацию на Rust в WebAssembly, включая синхронные API; случайные значения берутся из криптографической энтропии браузера. Операции path в браузере используют пути POSIX и виртуальный рабочий каталог /. Файлы в браузере хранятся в постоянном приватном хранилище приложения, а WebSockets используют транспорт браузера. Запуск процессов и вызовы «сырого» TCP в браузерах завершаются ошибкой code ERR_FEATURE_UNAVAILABLE_ON_PLATFORM.

#Сравнение с Node.js и gpui-shell

Модули следуют именам и формам Node для того подмножества, которое они экспортируют, и поведению gpui-shell там, где оно определено. Собственные решения NSH:

  • У fs/promises вызов exists возвращает true для символической ссылки, цель которой отсутствует, а unlink, rename, rmdir, lstat и readlink действуют на саму символическую ссылку.
  • У Stats и Dirent поля (size, mtimeMs, name, …) являются собственными свойствами, как в Node, а isFile(), isDirectory() и isSymbolicLink() находятся в их прототипе.
  • Слушатель ChildProcess получает полезную нагрузку события: текст вывода или { code, signal } завершения (с error и errorCode, когда дочерний процесс завершён средой выполнения). on возвращает дочерний процесс. run разрешается в { code, signal, stdout, stderr }: вывод в формате gpui-shell плюс сигнал, завершивший дочерний процесс.
  • Окружение дочернего процесса — небольшой набор от хоста плюс имена, которые разрешены приложению; процесс запускается в каталоге данных приложения или в cwd внутри корня fs.write (Процессы).
  • process.platform, process.arch, os.platform() и os.arch() возвращают имена из Rust std::env::consts, как и в gpui-shell: "macos", "windows", "linux", "ios", "android"; "aarch64", "x86_64" (Метаданные ОС).
  • process.dataDir равно null, если у хоста нет каталога данных; process.exit просит хост закрыть поверхность приложения.
  • Ограничения являются частью каждого API: размеры, количества и тайм-ауты перечислены ниже при каждом модуле.

#Ошибки

Функция, возвращающая promise, сообщает о любой ошибке отклонением promise, как это делает fs/promises в Node: неверный аргумент, путь или хост, которые приложению не разрешены, исчерпанный лимит и ошибка ввода-вывода обрабатываются одинаково, поэтому достаточно одного catch (или .catch). Синхронная функция (process.spawn, process.exit, os.homedir, ChildProcess.write, конструктор глобального WebSocket и send) выбрасывает исключение. Неверное использование (аргумент неверного типа, параметр вне допустимого диапазона) — это TypeError или RangeError. Каждая ошибка, которую приложение может обработать, содержит code, который не меняется от релиза к релизу; сообщение написано для людей и может измениться, поэтому приложения должны ветвиться по code:

codeЧто означает
ERR_ACCESS_DENIEDcapabilities приложения не разрешают этот вызов. У ошибки есть permission — ключ разрешения в nsh.json ("fs", "process", "network") — и resource — то, что было запрошено: путь, команда ("exit" для process.exit), "host:port" сокета или URL WebSocket
ERR_FEATURE_UNAVAILABLE_ON_PLATFORMВызов относится к другим платформам: файлы работают на настольных системах и в браузерах, процессы — на настольных системах, сокеты TCP — на нативных хостах, os.homedir() — на хостах, где настроен домашний каталог
ERR_NSH_QUOTA_EXCEEDEDИсчерпан лимит среды выполнения: операции или данные в обработке, дочерние процессы, сокеты, WebSockets, байты в очереди на отправку, слушатели
ETIMEDOUTВызов не уложился во время: run у timeoutMs, соединение, рукопожатие WebSocket, запись или закрытие
ENOENT, EEXIST, EACCES, ECONNREFUSED, …Ответ операционной системы, с кодами Node; EACCES и EPERM означают отказ ОС в доступе к файлу независимо от разрешений приложения
ERR_OUT_OF_RANGE, ERR_FS_FILE_TOO_LARGE, ERR_SOCKET_CLOSED, ERR_CHILD_PROCESS_STDIO_MAXBUFFERКоды Node для размера сверх лимита, закрытого соединения и вывода сверх захвата run
import fs from "fs/promises";

async function loadSettings(): Promise<string> {
  try {
    return await fs.readFile("${dataDir}/settings.json", "utf8");
  } catch (error) {
    if (error.code === "ENOENT") return "{}";
    if (error.code === "ERR_ACCESS_DENIED") console.warn(`not granted: ${error.permission} ${error.resource}`);
    throw error;
  }
}

Хост-приложение может выдать меньше разрешений, чем объявляет приложение (Песочница и разрешения), поэтому приложение должно обрабатывать ERR_ACCESS_DENIED так же, как неудавшийся запрос.

#Метаданные ОС и логирование

os.platform(): string и os.arch(): string сообщают фактическую нативную ОС и архитектуру хоста, а process.platform и process.arch содержат те же значения. Это имена из Rust std::env::consts::OS и ARCH, как и в gpui-shell: "macos", "windows", "linux", "ios", "android" для ОС, "aarch64", "x86_64" для архитектуры, поэтому приложения сравнивают с ними напрямую (process.platform === "macos"). os.EOL: string — разделитель строк хоста. os.homedir(): string возвращает настроенный абсолютный путь к домашнему каталогу хоста; если он не настроен, выбрасывается ошибка с code ERR_FEATURE_UNAVAILABLE_ON_PLATFORM. Пути к каталогам — это метаданные: файловые операции по-прежнему требуют разрешений fs.read / fs.write.

Браузерные приложения получают пустой объект process.env, а os.homedir() там выбрасывает ERR_FEATURE_UNAVAILABLE_ON_PLATFORM, поскольку домашние пути и переменные окружения относятся к нативным хостам.

import console from "console" использует глобальный объект console из VM. У модуля есть только экспорт по умолчанию, поддерживающий log, info, debug, warn, error, каждый с сигнатурой (...values: unknown[]): void. Он использует форматирование логгера NSH.

#Сжатие

zlib взят из LLRT с одним ограничением, добавленным в NSH: каждая распаковка (inflate, inflateRaw, gunzip, brotliDecompress, zstdDecompress и их формы Sync) выдаёт не более 64 MiB или предел кучи VM, если он меньше. Более длинный вывод приводит к исключению RangeError, как в Node.js для maxOutputLength; собственный maxOutputLength приложения задаёт более низкую границу для одного вызова. Buffer.alloc, Buffer.allocUnsafe, Buffer.allocUnsafeSlow и crypto.randomBytes выбрасывают RangeError (code ERR_OUT_OF_RANGE) для размера, превышающего предел кучи VM, или 1 GiB, если предела нет; allocUnsafe возвращает обнулённую память.

#Файлы

fs/promises читает и записывает нативные файлы на настольных хостах (macOS, Windows и Linux) и приватные файлы приложения в браузере через Origin Private File System (OPFS). Каждый путь проверяется по capabilities.fs до начала операции: корни read для readFile, readdir, stat, lstat, readlink и exists, корни write для writeFile, mkdir, rename, unlink и rmdir. Любая ошибка отклоняет promise: путь вне разрешений отклоняется с code ERR_ACCESS_DENIED (permission "fs", resource — путь в том виде, как его записало приложение) до какой-либо работы с диском.

{
  "capabilities": {
    "fs": { "read": ["${dataDir}"], "write": ["${dataDir}"] }
  }
}

Корни — это абсолютные пути, ${dataDir} (каталог данных этого приложения, выбираемый хостом), ${pluginDir} (каталог приложения), ${homeDir} или пути относительно каталога приложения. Относительный путь в вызове по очереди разрешается в каждом выданном корне. Встраивающий хост может сузить разрешения ещё сильнее. Каждая операция остаётся внутри выданного корня, включая символические ссылки; абсолютный путь проверяется в том виде, как он записан, поэтому корня достигают по его собственному (или разрешённому) пути, а не через ссылку извне. lstat, readlink, exists, unlink, rename и rmdir действуют на саму символическую ссылку: exists возвращает true для символической ссылки, цель которой отсутствует, а unlink удаляет ссылку и сохраняет её цель.

Ошибки несут свойства ошибок Node: code (ENOENT, EEXIST, ENOTDIR, EISDIR, ENOTEMPTY, ELOOP, EACCES, EPERM, …; EILSEQ для текста не в UTF-8, ERR_FS_FILE_TOO_LARGE при превышении лимита чтения), syscall и path, поэтому error.code === "ENOENT" позволяет отличить отсутствующий файл. Путь вне разрешений отклоняется с ERR_ACCESS_DENIED, вызов на iOS или Android (приложения которых хранят данные через Storage) — с ERR_FEATURE_UNAVAILABLE_ON_PLATFORM, а неверный аргумент (неизвестная кодировка или параметр) — с TypeError.

readFile возвращает Uint8Array или строку при указании "utf8" и читает файлы размером до 64 MiB. writeFile записывает строку или Uint8Array размером до 8 MiB, создавая или заменяя обычный файл. readdir перечисляет до 10 000 имён (суммарно 1 MiB; больший каталог отклоняется с RangeError, code ERR_OUT_OF_RANGE) в отсортированном виде или Dirent (name, isDirectory()) при указании { withFileTypes: true }. stat и lstat возвращают Stats с size, mode, mtimeMs / atimeMs / birthtimeMs, mtime / atime / birthtime в виде Date, а также isFile(), isDirectory() и isSymbolicLink(). rmdir удаляет один пустой каталог. Одна VM выполняет до 1024 операций одновременно и удерживает до 64 MiB файловых данных в обработке (данные, переданные в writeFile, пока они не записаны, и данные, прочитанные до того, как их promise завершится); вызов сверх этих лимитов отклоняется с ERR_NSH_QUOTA_EXCEEDED, а writeFile сверх 8 MiB — с RangeError (code ERR_OUT_OF_RANGE).

Хранилище браузера ограничено источником (origin) страницы и собственным пространством имён приложения, с теми же разрешениями на файлы и теми же лимитами размера, проверяемыми в Rust. OPFS требует защищённого контекста, например HTTPS или localhost. Время изменения файлов берётся из метаданных браузера; неизвестные время доступа, время создания и режимы равны нулю. rename использует атомарное перемещение через дескриптор браузера и сообщает ENOTSUP, если браузер его не поддерживает. Элементы в браузере — это обычные файлы или каталоги, а readlink сообщает EINVAL.

#Процессы

process.spawn и process.run запускают команды на настольных хостах (macOS, Windows и Linux). Рантайм исполняет программу напрямую с массивом аргументов, без оболочки, и владеет своими каналами; stdout и stderr поступают как инкрементальный UTF-8.

{
  "capabilities": {
    "process": { "execute": ["git"], "env": ["GIT_AUTHOR_NAME"] },
    "fs": { "write": ["${dataDir}"] }
  }
}

Команды сопоставляются с process.execute точно: git и /usr/bin/git — это отдельные разрешения. Простые команды разрешаются через абсолютные каталоги PATH хоста; в Windows простая команда также находит там <name>.com и <name>.exe. Пакетный файл (.cmd, .bat) запускается через cmd.exe: разрешите его абсолютный путь (C:\Windows\System32\cmd.exe) и передайте /d, /c и скрипт в качестве аргументов. Окружение дочернего процесса состоит из PATH, HOME, USERPROFILE, SystemRoot, TEMP, TMP, TMPDIR, LANG и LC_* хоста, а также имён, которые разрешает process.env (совпадение точное, без учёта регистра только в Windows, окружение которой регистр не различает). Дочерний процесс запускается в каталоге данных приложения (создаётся, если отсутствует) или в cwd — существующем каталоге внутри корня fs.write, поскольку дочерний процесс может писать там, где запущен. Встраивающий хост может сузить разрешения.

use std::{
    io::{BufRead, BufReader},
    process::{Command, Stdio},
};

spawn(async move {
    // `run`: the whole output once the command exits.
    if let Ok(Ok(output)) = blocking(|| Command::new("git").args(["status", "--short"]).output()).await {
        log::info!("{} {}", output.status, String::from_utf8_lossy(&output.stdout));
    }
    // `spawn` with `lines: true`: each line as it comes.
    let _ = blocking(|| -> std::io::Result<()> {
        let mut child = Command::new("git").args(["log", "--oneline"]).stdout(Stdio::piped()).spawn()?;
        if let Some(stdout) = child.stdout.take() {
            for line in BufReader::new(stdout).lines() {
                log::info!("{}", line?);
            }
        }
        log::info!("done {}", child.wait()?);
        Ok(())
    })
    .await;
});

run завершается с { code, signal, stdout, stderr } при любом статусе выхода: code — статус выхода, а signal null, либо, если дочерний процесс завершил сигнал, code равно null, а signal — имя сигнала ("SIGTERM"). При любом сбое промис отклоняется. spawn сразу возвращает дочерний процесс; stdin: "pipe" включает write и end, а lines: true доставляет полные строки. on(event, handler) возвращает дочерний процесс, а обработчик получает полезную нагрузку события: текст или { code, signal } один раз, после последнего вывода (с error, когда рантайм сам завершил дочерний процесс: таймаут, слишком длинная строка) (сразу — для слушателя, добавленного позже). pid — идентификатор дочернего процесса. spawn выбрасывает то, чем отклоняется run, до запуска дочернего процесса: команда, которую process.execute не разрешает, завершается с ERR_ACCESS_DENIED (permission "process", resource — команда), cwd вне разрешений fs.write — с ERR_ACCESS_DENIED (permission "fs", resource — cwd), вызов вне настольных хостов — с ERR_FEATURE_UNAVAILABLE_ON_PLATFORM, а вызов сверх лимита дочерних процессов — с ERR_NSH_QUOTA_EXCEEDED; сбой запуска содержит code (ENOENT для отсутствующего исполняемого файла, EACCES для файла, который ОС не может запустить), syscall (spawn <command>) и path. run отклоняется с ETIMEDOUT по истечении срока и с RangeError (code ERR_CHILD_PROCESS_STDIO_MAXBUFFER) при выводе сверх лимита захвата; выход запущенного дочернего процесса несёт то же сообщение в error, а его код — в errorCode. process.dataDir равно null, если у хоста нет каталога данных. kill, таймаут, очистка реактивного владельца и завершение VM останавливают всё дерево процессов: в Unix его группа процессов получает SIGTERM, а затем SIGKILL через 250 мс (при завершении VM — сразу SIGKILL).

ЛимитЗначение
timeoutMsДо 4 294 967 295 мс; run по умолчанию 30 секунд, spawn работает до завершения
Захват run8 МиБ на поток
Одна строка с lines: true8 МиБ
Дочерних процессов на VM32
Вывод «в полёте» на VM8 МиБ; читатели ждут, пока JS заберёт его
Ожидающий stdin на дочерний процесс1 МиБ; при превышении write выбрасывает ERR_NSH_QUOTA_EXCEEDED
Слушатели64 на событие на дочерний процесс

process.exit(code?) запрашивает у хоста закрытие этой поверхности приложения. Разрешите его через "exit": true в capabilities.process; движок пересылает запрос через платформенную команду выхода из приложения. Вызов, не покрытый разрешением, выбрасывает ERR_ACCESS_DENIED (permission "process", resource "exit"). Хосты принимают запросы на выход через установленный ими обработчик выхода; в остальных случаях вызов выбрасывает ERR_FEATURE_UNAVAILABLE_ON_PLATFORM. В macOS это закрывает окно поверхности. В браузере это останавливает приложение и очищает его поверхность, оставляя страницу хоста открытой. Встраивающий хост сам решает, как его поверхность обрабатывает запрос. Gallery запрещает эту возможность, чтобы действие Request host exit демонстрировало обработку разрешений и не закрывало демо.

#Сетевые разрешения и границы

Глобальный fetch сохраняет потоковую HTTP-реализацию NSH, проверяя network.hosts или network.http для запросов и переходов по редиректам. WebSocket поддерживает ws: / wss:, текстовые и бинарные сообщения, слушатели событий и закрывающее рукопожатие. Рукопожатие WebSocket — это HTTP GET, поэтому и глобальный WebSocket, и connect модуля websocket используют HTTP-разрешения: ws проверяется как http, а wss — как https, с методом GET (правило также может перечислять ws / wss в schemes). NSH проверяет исходный нормализованный URL до начала соединения. Нативное рукопожатие подключается к этому URL и отклоняет редиректы; редиректы и политика соединений WebSocket в браузере определяются самим браузером. Оба API отказываются принимать URL с учётными данными или фрагментом. Каждая VM удерживает не более 32 WebSocket на оба API вместе, а сообщения не превышают 8 МиБ в обоих.

URL, не разрешённый приложению, отклоняется до начала любого соединения. Глобальный WebSocket сообщает об этом так же, как браузер сообщает о сбое соединения: конструктор возвращает сокет, который затем вызывает error и close (код 1006, wasClean false), так что обработчики, которые приложение задаёт сразу после конструктора, их услышат. connect модуля отклоняется с ERR_ACCESS_DENIED (permission "network", resource — URL). 33-й WebSocket выбрасывает ошибку (конструктор) или отклоняется (connect) с ERR_NSH_QUOTA_EXCEEDED, а некорректный URL — это SyntaxError (конструктор) или TypeError (connect).

Глобальный WebSocket ставит в очередь на отправку не более восьми сообщений, с буфером не более 8 МиБ (bufferedAmount); send() сверх любого из лимитов выбрасывает ERR_NSH_QUOTA_EXCEEDED, а когда сокет закрывается, send() отбрасывает свои данные, как это делает браузерный. Его рукопожатие и каждая запись завершаются по таймауту через 30 секунд, а закрытие ждёт close-фрейм от пира до 5 секунд. Сообщения доставляются событиями message по мере поступления.

WebSocketConnection модуля websocket удерживает не более восьми ожидающих операций (чтение, запись, закрытие) и не более 8 МиБ ещё не отправленных записей; write() сверх любого из лимитов отклоняется с ERR_NSH_QUOTA_EXCEEDED, а сообщение больше 8 МиБ — с RangeError (code ERR_OUT_OF_RANGE). Одновременно может ожидать только один read(); второй отклоняется с TypeError. Сообщения, пришедшие до прочтения, ждут приложение, а их поток следует за чтением: пока восемь сообщений или 8 МиБ остаются непрочитанными, соединение прекращает чтение из сети, так что отправка пира тоже ждёт, а чтение возобновляется по мере чтения приложением. Разрешение имени, подключение и рукопожатие вместе, каждая запись и закрытие завершаются по таймауту через 30 секунд.

read() возвращает сообщения в порядке поступления. Когда соединение закрыто штатно (пир закрыл его или приложение вызвало close()) и все предшествующие сообщения прочитаны, read() возвращает null, как это делает net-сокет при EOF; closeCode и closeReason тогда содержат код и причину из close-фрейма (null, пока соединение открыто; 1005 для close-фрейма без кода, 1006 для соединения, завершившегося без закрывающего рукопожатия). read() отклоняется только при сбое, после предшествующих сообщений, с его code: ERR_SOCKET_CLOSED для соединения, завершившегося без закрывающего рукопожатия, ETIMEDOUT, код ОС для ошибки сокета, или EPROTO для ошибки протокола (с HTTP status отклонённого рукопожатия). close(code?, reason?) отправляет свой код (1000 по умолчанию или от 3000 до 4999) и причину (не более 123 байт UTF-8) в close-фрейме, как это делает close() глобального WebSocket; код или причина вне этих рамок отклоняются с RangeError (code ERR_OUT_OF_RANGE). Он отбрасывает ещё не прочитанные сообщения и завершается, когда пир отвечает на закрывающее рукопожатие.

import { connect } from "websocket";

const socket = await connect("wss://chat.example.com/socket");
await socket.write("hello");
for (let message; (message = await socket.read()) !== null; ) {
  console.log(message);
}
console.log(`closed: ${socket.closeCode} ${socket.closeReason}`);

net.connect({ host, port }) использует только network.sockets — список точных адресов назначения "host:port" ("[::1]:9000" для IPv6), проверяемых до DNS-запроса. Каждая запись разрешает один порт на одном хосте; шаблоны хостов, network.hosts и HTTP-разрешения покрывают только HTTP-трафик, а приложение без "capabilities" перечисляет и свои сокеты. Возвращаемый Socket предоставляет read(size?), write(text) и close(); чтение возвращает Uint8Array или null при EOF. Любой сбой отклоняет промис: неразрешённое назначение — с ERR_ACCESS_DENIED (permission "network", resource "host:port"), 33-й сокет и записи сверх 4 МиБ, которые могут ожидать, — с ERR_NSH_QUOTA_EXCEEDED, чтение или запись свыше 1 МиБ — с RangeError (code ERR_OUT_OF_RANGE); сбои соединения и ввода-вывода отклоняются с code из Node (ECONNREFUSED, ENOTFOUND, ETIMEDOUT, ECONNRESET, …) и syscall, сбои соединения — также с address и port, а запись в закрытый сокет — с ERR_SOCKET_CLOSED (чтение там возвращает null). У соединений и ввода-вывода таймаут 30 секунд, каждое чтение/запись ограничено 1 МиБ, в ожидании отправки может находиться не более 4 МиБ записей, а close() немедленно завершает ожидающие чтения и записи. Каждая VM держит не более 32 сокетов открытыми или в процессе подключения. Это клиентский интерфейс сокетов на основе промисов.

Сырой TCP работает на нативных хостах. Оба интерфейса WebSocket в веб-приложениях используют браузерный транспорт, сохраняя проверки возможностей на Rust и описанные выше лимиты. Пользовательские заголовки рукопожатия в браузере приводят к явной ошибке неподдерживаемой опции. Политики соединений браузера также действуют. Разрешения встраивающего хоста пересекаются с декларацией возможностей приложения.

#Пример разрешения для WebSocket

Этот фрагмент nsh.json разрешает GET-рукопожатие для данного URL wss, сохраняя его путь и область HTTPS:

{
  "capabilities": {
    "network": {
      "http": [{
        "host": "chat.example.com",
        "methods": ["GET"],
        "schemes": ["https"],
        "pathPrefix": "/socket"
      }]
    }
  }
}
use futures_util::{SinkExt, StreamExt};
use tokio_tungstenite::{connect_async, tungstenite::Message};

spawn(async move {
    let _ = background(async {
        let (mut socket, _) = connect_async("wss://chat.example.com/socket").await?;
        socket.send(Message::text("hello")).await?;
        while let Some(message) = socket.next().await {
            log::info!("{}", message?);
        }
        Ok::<_, tokio_tungstenite::tungstenite::Error>(())
    })
    .await;
});

#Пример разрешения для TCP

TCP использует разрешение для сокета: точный хост и порт, к которым подключается приложение.

{
  "capabilities": {
    "network": { "sockets": ["tcp.example.com:9000"] }
  }
}
use std::{
    io::{Read, Write},
    net::TcpStream,
};

spawn(async move {
    let reply = blocking(|| -> std::io::Result<String> {
        let mut socket = TcpStream::connect(("tcp.example.com", 9000))?;
        socket.write_all(b"hello\n")?;
        let mut bytes = [0; 1024];
        let n = socket.read(&mut bytes)?;
        Ok(String::from_utf8_lossy(&bytes[..n]).into_owned())
    })
    .await;
    if let Ok(Ok(reply)) = reply {
        log::info!("{reply}");
    }
});

Откройте Standard APIs в examples/gallery, чтобы запустить файловые операции, дочерние процессы, TCP и оба интерфейса WebSocket, а также просмотреть метаданные хоста и задачи в очереди. Действие utilities демонстрирует буферы, хеширование, сжатие и файловые URL. Каждое действие показывает свой результат или ошибку. Файлы остаются во временном каталоге внутри собственного каталога данных Gallery и удаляются после действия. Действия TCP и WebSocket подключаются к локальным эхо-серверам на портах 9876 и 9877. В вебе файловые действия работают с OPFS, utilities выполняются в WebAssembly, а оба действия WebSocket используют браузерные соединения; действия с процессами и TCP показывают типизированные платформенные ошибки хоста.

AI Chat в Gallery использует process.spawn для чата через локально установленный Claude или Codex CLI на десктопе. Войдите через CLI в терминале, затем выберите провайдера в поле ввода. Ответы используют разговор как контекст; Stop и уход с экрана завершают дочерний процесс. В вебе и на мобильных запускаются сценарные потоковые разговоры, включая рассуждения, карточки инструментов и восстановление после ошибок.