تُرجمت هذه الصفحة آليًا وقد تحتوي على أخطاء. اقرأ الأصل الإنجليزي

API

الوحدات القياسية

يوفّر Native Shell إحدى عشرة وحدة قياسية لـ TSX، تُستورد بأسمائها المجرّدة (تستخدم شيفرة Rust مكتبات Rust نفسها: من Rust): واجهات برمجية بشكل Node متوافقة مع واجهات gpui-shell، تعمل في آلة VM الخاصة بالتطبيق على بيئة NSH الأصلية، مع التحقق من صلاحياتها في Rust. تُولَّد تصريحات TypeScript الخاصة بها في .nsh/types من المواصفات نفسها التي تُولَّد منها الوحدات، لذا فما تعرضه الأنواع هو ما يصدّره وقت التشغيل.

الوحدةالصادرات
bufferBuffer، atob، btoa، constants، والتصدير الافتراضي (LLRT)؛ وBuffer متاح أيضًا كمتغيّر عام
cryptocreateHash، createHmac، randomBytes، randomFill، randomFillSync، randomInt، randomUUID، getRandomValues، webcrypto، crypto، Crc32، Crc32c، Md5، Sha1، Sha256، Sha384، Sha512، والتصدير الافتراضي (LLRT)؛ وcrypto متاح أيضًا كمتغيّر عام
pathbasename، delimiter، dirname، extname، format، isAbsolute، join، normalize، parse، relative، resolve، sep، والتصدير الافتراضي (LLRT)
urlURL، URLSearchParams، domainToASCII، domainToUnicode، fileURLToPath، format، pathToFileURL، urlToHttpOptions، والتصدير الافتراضي؛ وURL وURLSearchParams صنفان عامّان في NSH
zlibdeflate، deflateRaw، inflate، inflateRaw، gzip، gunzip، brotliCompress، brotliDecompress، zstdCompress، zstdDecompress، ولكلٍّ منها صيغة Sync، والتصدير الافتراضي (LLRT)
osplatform()، arch()، homedir()، EOL، والتصدير الافتراضي
consoleالتصدير الافتراضي: كائن console الخاص بالـ VM (log، info، debug، warn، error)
fs/promisesreadFile، writeFile، readdir، exists، mkdir، rename، unlink، rmdir، stat، lstat، readlink، والتصدير الافتراضي
processrun، spawn، exit، platform، arch، nextTick، ChildProcess، والتصدير الافتراضي؛ ولكائن التصدير الافتراضي أيضًا env وdataDir، وهو المتغيّر العام process
netconnect، Socket، والتصدير الافتراضي
websocketconnect(url, options?) الذي يُحلّ إلى WebSocketConnection (read، write، close(code?, reason?)، closeCode، closeReason)، وWebSocketConnection، والتصدير الافتراضي؛ والباني العام 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]، وcrate base64
cryptocrates مثل sha2 وhmac وrand وuuid
pathstd::path
urlcrate url
zlibcrates مثل flate2 وbrotli وzstd
osstd::env::consts، os()
consolecrate log
fs/promisesstd::fs في blocking(…)؛ وStorage لبيانات المفتاح والقيمة
processstd::process::Command في blocking(…)
netstd::net::TcpStream في blocking(…)، أو tokio::net في background(…)
websocketcrate مثل tokio-tungstenite في background(…)

#الاستيراد والأنواع المولَّدة

تُستورد كل وحدة بالاسم الوارد في الجدول تمامًا: import { join } from "path"، import fs from "fs/promises". يُبقي المُجمِّع (bundler) هذه الاستيرادات كما هي، ويقدّمها وقت التشغيل أصليًا؛ والفهرس هو هذه الأسماء الأحد عشر. buffer وcrypto وpath وurl وzlib هي تنفيذات LLRT، مثبّتة على d9fd873e793c6551c5d92b2e31480f3279e3cd8d، مع تصريحات مُسقَطة من تلك المراجعة على الصادرات الموجودة في وقت التشغيل. أما البقية فهي من تنفيذ NSH نفسها. وتحتفظ NSH بهوية أصناف URL وBlob والتدفقات والأحداث الخاصة بها.

تُحمَّل الوحدات الإحدى عشرة جميعها على المضيفات الأصلية وفي تطبيقات المتصفح. تستخدم Buffer والتجزئة والتشفير والضغط التنفيذ المحمول نفسه المكتوب بـ Rust في WebAssembly، بما فيها الواجهات المتزامنة؛ وتستخدم القيم العشوائية إنتروبيا التشفير في المتصفح. تستخدم عمليات path في المتصفح مسارات POSIX ودليل عمل افتراضيًا /. تستخدم ملفات المتصفح تخزينًا دائمًا خاصًا بالتطبيق، وتستخدم WebSockets ناقل المتصفح. يفشل تنفيذ العمليات واستدعاءات TCP الخام بالخطأ code ERR_FEATURE_UNAVAILABLE_ON_PLATFORM في المتصفحات.

#مقارنتها بـ Node.js وgpui-shell

تتبع الوحدات أسماء Node وأشكاله للمجموعة الجزئية التي تصدّرها، وسلوك gpui-shell حيثما يحدّد سلوكًا. أما اختيارات NSH الخاصة فهي:

  • تكون exists في fs/promises هي true عندما يكون هدف الرابط الرمزي مفقودًا، وتعمل unlink وrename وrmdir وlstat وreadlink على الرابط الرمزي نفسه.
  • في Stats وDirent تكون حقولهما (size، mtimeMs، name، …) خصائص خاصة بالكائن نفسه كما في Node، بينما توجد isFile() وisDirectory() وisSymbolicLink() على النموذج الأولي (prototype).
  • يتلقى مستمع ChildProcess حمولة الحدث: نص المخرجات، أو { code, signal } الخاص بالخروج (مع error وerrorCode عندما ينهي وقت التشغيل العملية الابن). تُرجع on العملية الابن. وتُحلّ run إلى { code, signal, stdout, stderr }: مخرجات gpui-shell مع الإشارة التي أنهت العملية الابن.
  • تتكوّن بيئة العملية الابن من مجموعة صغيرة من المضيف إضافة إلى الأسماء الممنوحة للتطبيق، وتعمل في دليل بيانات التطبيق أو في cwd ضمن جذر fs.write (العمليات).
  • تعطي process.platform وprocess.arch وos.platform() وos.arch() أسماء std::env::consts في Rust، كما تفعل نظيراتها في gpui-shell: "macos"، "windows"، "linux"، "ios"، "android"؛ "aarch64"، "x86_64" (بيانات نظام التشغيل الوصفية).
  • تكون process.dataDir هي null عندما لا يكون للمضيف دليل بيانات؛ وتطلب process.exit من المضيف إغلاق واجهة التطبيق.
  • الحدود جزء من كل واجهة برمجية: تُدرج الأحجام والأعداد ومهلات الانتظار مع كل وحدة أدناه.

#حالات الفشل

تُبلغ الدالة التي تُرجع وعدًا (promise) عن كل فشل برفضه، كما تفعل fs/promises في Node: الوسيط الخاطئ، والمسار أو المضيف غير الممنوح للتطبيق، والحد المستنفد، وخطأ الإدخال/الإخراج على حد سواء، ولذا يكفي catch واحد (أو .catch) للتعامل معها كلها. أما الدالة المتزامنة (process.spawn، process.exit، os.homedir، ChildProcess.write، وباني WebSocket العام وsend) فتُطلق استثناءً. الاستخدام الخاطئ (وسيط من نوع خاطئ، أو خيار خارج نطاقه) هو TypeError أو RangeError. يحمل كل فشل يستطيع التطبيق معالجته code، يبقى ثابتًا من إصدار إلى آخر؛ أما الرسالة فهي مكتوبة للبشر وقد تتغير، لذا تتفرّع التطبيقات بحسب code:

codeالمعنى
ERR_ACCESS_DENIEDلا تمنح capabilities الخاصة بالتطبيق هذا الاستدعاء. يحتوي الخطأ على 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انتهت مهلة الاستدعاء: timeoutMs الخاص بـ run، أو اتصال، أو مصافحة 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 القيم نفسها. وهي أسماء std::env::consts::OS وARCH في Rust، كما هي في 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. وهي تستخدم تنسيق المسجِّل (logger) الخاص بـ NSH.

#الضغط

zlib هي من LLRT، مع حدّ واحد تضيفه NSH: ينتج كل فك ضغط (inflate، inflateRaw، gunzip، brotliDecompress، zstdDecompress وصيغ Sync الخاصة بها) ما لا يزيد على 64 MiB، أو حدّ كومة الذاكرة (heap) الخاص بالـ 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. يرفض كل فشل الوعد: فالمسار الواقع خارج الصلاحيات يُرفض بـ code ERR_ACCESS_DENIED (permission "fs"، وresource هو المسار كما كتبه التطبيق)، قبل أي عمل على القرص.

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

الجذور مسارات مطلقة، أو ${dataDir} (دليل بيانات هذا التطبيق، يختاره المضيف)، أو ${pluginDir} (دليل التطبيق)، أو ${homeDir}، أو مسارات نسبية إلى دليل التطبيق. يُحلّ المسار النسبي في الاستدعاء داخل كل جذر ممنوح بالتتابع. وقد يضيّق المضيف المضمِّن (embedding host) الصلاحيات أكثر. تبقى كل عملية داخل جذرها الممنوح، بما في ذلك الروابط الرمزية؛ ويُتحقق من المسار المطلق كما كُتب، لذا يُوصل إلى الجذر بمساره نفسه (أو بمساره المحلول)، لا عبر رابط من خارجه. تعمل 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 حتى تُكتب، والبيانات المقروءة حتى يستقر وعدها)؛ ويُرفض الاستدعاء الذي يتجاوز هذه الحدود بـ ERR_NSH_QUOTA_EXCEEDED، وwriteFile يتجاوز 8 MiB بـ RangeError (code ERR_OUT_OF_RANGE).

يقتصر تخزين المتصفح على أصل الصفحة ومساحة الأسماء الخاصة بالتطبيق، مع صلاحيات الملفات وحدود الأحجام نفسها التي يُتحقق منها في Rust. يتطلب OPFS سياقًا آمنًا، مثل HTTPS أو localhost. تأتي أوقات تعديل الملفات من البيانات الوصفية للمتصفح؛ وتكون أوقات الوصول/الإنشاء والأذونات (modes) غير المعروفة صفرًا. تستخدم rename نقلًا ذريًا لمقبض المتصفح وتُبلغ عن ENOTSUP عندما لا يوفّره المتصفح. مدخلات المتصفح هي ملفات عادية أو أدلة، وتُبلغ readlink عن EINVAL.

#العمليات (Processes)

process.spawn وprocess.run تشغّلان الأوامر على المضيفات المكتبية (macOS وWindows وLinux). ينفّذ وقت التشغيل البرنامج مباشرة مع مصفوفة وسائط ودون shell، ويمتلك أنابيبه؛ ويصل 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 ms (وعند الإنهاء، SIGKILL فورًا).

الحدالقيمة
timeoutMsحتى 4,294,967,295 ms؛ القيمة الافتراضية لـ run هي 30 ثانية، وspawn يعمل حتى الخروج
حد التقاط run8 MiB لكل تدفق
سطر واحد مع lines: true8 MiB
العمليات الابن لكل VM32
المخرجات قيد النقل لكل VM8 MiB؛ تنتظر القارئات حتى تأخذها JS
stdin المعلّق لكل عملية ابن1 MiB؛ يرمي 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 العام بتنفيذ NSH لـ HTTP المتدفق، ويتحقق من network.hosts أو network.http للطلبات ولقفزات إعادة التوجيه. يدعم WebSocket كلًّا من ws: / wss:، والرسائل النصية والثنائية، ومستمعي الأحداث، ومصافحات الإغلاق. مصافحة WebSocket هي طلب HTTP GET، لذا يستخدم كل من WebSocket العام وconnect الخاص بوحدة websocket منح HTTP: يُفحص ws بوصفه http وwss بوصفه https، باستخدام GET (ويجوز لقاعدة أن تدرج أيضًا ws / wss في schemes). يفحص NSH عنوان URL الأولي المُطبَّع قبل بدء الاتصال. تتصل المصافحة الأصلية بذلك العنوان وترفض إعادة التوجيه؛ أما إعادة توجيه WebSocket في المتصفح وسياسة الاتصال فيتحكم بهما المتصفح. ترفض كلتا الواجهتين عنوان URL يحتوي على بيانات اعتماد أو جزء (fragment). يحتفظ كل VM بحد أقصى 32 اتصال WebSocket عبر الواجهتين معًا، وتبلغ الرسائل حدًّا أقصى 8 MiB في كلتيهما.

يُرفض أي عنوان URL غير ممنوح للتطبيق قبل بدء أي اتصال. يبلّغ WebSocket العام عنه كما يبلّغ المتصفح عن اتصال فاشل: يُرجع المُنشئ المقبس، الذي يطلق بعد ذلك error وclose (الرمز 1006، wasClean false)، فتسمعهما المعالجات التي يضبطها التطبيق مباشرة بعد المُنشئ. أما connect الخاص بالوحدة فيُرفض بالخطأ ERR_ACCESS_DENIED (permission "network"، وresource هو عنوان URL). يرمي WebSocket رقم 33 خطأً (في المُنشئ) أو يُرفض (connect) بالخطأ ERR_NSH_QUOTA_EXCEEDED، ويُعدّ عنوان URL المشوّه SyntaxError (في المُنشئ) أو TypeError (connect).

يُدرج WebSocket العام في الطابور ما لا يزيد على ثماني رسائل للإرسال، مع تخزين مؤقت بحد أقصى 8 MiB (bufferedAmount)؛ ويرمي send() الذي يتجاوز أيًّا منهما ERR_NSH_QUOTA_EXCEEDED، وعندما يكون المقبس في طور الإغلاق يتجاهل send() بياناته، كما يفعل في المتصفح. تنتهي مهلة مصافحته وكل عملية كتابة بعد 30 ثانية، وينتظر الإغلاق حتى 5 ثوانٍ لإطار الإغلاق من النظير. تُسلَّم الرسائل كأحداث message فور وصولها.

يحتفظ WebSocketConnection الخاص بوحدة websocket بما لا يزيد على ثماني عمليات معلّقة (قراءات وكتابات وإغلاق) وما لا يزيد على 8 MiB من الكتابات التي لم تُرسل بعد؛ ويُرفض write() الذي يتجاوز أيًّا منهما بالخطأ ERR_NSH_QUOTA_EXCEEDED، وتُرفض الرسالة التي تتجاوز 8 MiB بالخطأ RangeError (code ERR_OUT_OF_RANGE). يجوز أن ينتظر read() واحد في كل مرة؛ ويُرفض الثاني بالخطأ TypeError. تنتظر الرسائل التي تصل قبل قراءتها التطبيق، ويتبع تدفقها قراءاته: فما دامت ثماني رسائل أو 8 MiB تنتظر دون قراءة، يتوقف الاتصال عن القراءة من الشبكة، فينتظر إرسال النظير أيضًا، وتُستأنف القراءة عندما يقرأ التطبيق. تنتهي مهلة التحليل والاتصال والمصافحة معًا، وكل كتابة، والإغلاق، بعد 30 ثانية.

يحقّق read() الرسائل بترتيب وصولها. وبمجرد إغلاق الاتصال بشكل سليم (أغلقه النظير، أو استدعى التطبيق close()) وقراءة كل الرسائل التي سبقت ذلك، يُحقَّق read() بالقيمة null، كما يفعل مقبس net عند EOF؛ ثم يحمل closeCode وcloseReason رمز إطار الإغلاق وسببه (null أثناء بقاء الاتصال مفتوحًا؛ و1005 لإطار إغلاق بلا رمز، و1006 لاتصال انتهى دون مصافحة إغلاق). يُرفض read() عند الفشل فقط، بعد الرسائل التي سبقته، مع code الخاص به: ERR_SOCKET_CLOSED لاتصال انتهى دون مصافحة إغلاق، وETIMEDOUT، وهو رمز نظام التشغيل لخطأ في المقبس، أو EPROTO لخطأ في البروتوكول (مع status الخاص بـ HTTP لمصافحة مرفوضة). يرسل close(code?, reason?) رمزه (1000، وهو الافتراضي، أو من 3000 إلى 4999) وسببه (بحد أقصى 123 بايت UTF-8) في إطار الإغلاق، كما يفعل 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 MiB المسموح بانتظارها تُرفض بالخطأ ERR_NSH_QUOTA_EXCEEDED، وقراءة أو كتابة تتجاوز 1 MiB تُرفض بالخطأ RangeError (code ERR_OUT_OF_RANGE)؛ وتُرفض إخفاقات الاتصال والإدخال/الإخراج مع code الخاص بـ Node (ECONNREFUSED وENOTFOUND وETIMEDOUT وECONNRESET وغيرها) وsyscall، ويحمل إخفاق الاتصال أيضًا address وport، وتُرفض الكتابة على مقبس مغلق بالخطأ ERR_SOCKET_CLOSED (وتُرجع القراءة هناك null). للاتصالات والإدخال/الإخراج مهلات مدتها 30 ثانية، وكل قراءة/كتابة محدودة بـ 1 MiB، ويمكن أن تنتظر ما لا يزيد على 4 MiB من الكتابات قبل إرسالها، ويُنهي close() القراءات والكتابات المعلّقة فورًا. يُبقي كل VM ما لا يزيد على 32 مقبسًا مفتوحًا أو قيد الاتصال. هذه واجهة مقبس عميل قائمة على الوعود (promises).

يعمل TCP الخام على المضيفات الأصلية. تستخدم واجهتا WebSocket كلتاهما نقل المتصفح في تطبيقات الويب، مع الإبقاء على فحوص الصلاحيات في Rust والحدود المذكورة أعلاه. وتُبلّغ ترويسات المصافحة المخصصة في المتصفح عن خطأ صريح بعدم دعم الخيار. كما تسري سياسات اتصال المتصفح. وتتقاطع منح المُضمِّن مع إعلان صلاحيات التطبيق.

#مثال على منحة WebSocket

يسمح هذا المقطع من nsh.json بمصافحة GET لعنوان 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، وللاطلاع على بيانات المضيف الوصفية والمهام المصطفّة. يوضّح إجراء الأدوات المساعدة المخازن المؤقتة والتجزئة والضغط وعناوين URL للملفات. يعرض كل إجراء نتيجته أو خطأه. تبقى الملفات في دليل مؤقت ضمن دليل بيانات Gallery نفسه وتُزال بعد الإجراء. تتصل إجراءات TCP وWebSocket بخوادم echo محلية على المنفذين 9876 و9877. على الويب، تستخدم إجراءات الملفات OPFS، وتعمل الأدوات المساعدة في WebAssembly، وتستخدم إجراءا WebSocket كلاهما اتصالات المتصفح؛ أما إجراءات العمليات وTCP فتعرض أخطاء المنصة المصنّفة الخاصة بالمضيف.

يستخدم AI Chat في Gallery process.spawn للدردشة عبر Claude أو Codex CLI المثبّت محليًا على سطح المكتب. سجّل الدخول عبر CLI في الطرفية، ثم اختر المزوّد في حقل الإدخال. تستخدم الردود المحادثة سياقًا؛ ويؤدي Stop ومغادرة الشاشة إلى إنهاء العملية الابن. تشغّل منصتا الويب والجوال محادثات متدفقة مكتوبة مسبقًا، تشمل الاستدلال وبطاقات الأدوات والتعافي من الأخطاء.