البدء مع URnetwork SDK
ثبّت SDK بمدير الحزم الخاص بلغتك، ثم استكشف أمثلة اللغات ودليل المقابس.
يأخذ هذا الدليل المطوِّر من مشروع فارغ إلى أول اتصال: احصل على رمز JWT للحساب، وثبّت طبقة الربط الخاصة بمنصتك، وارفع جهازاً، وتأكّد من تدفق الحركة. وSDK وحدةُ Go واحدة، github.com/urnetwork/sdk، معروضة عبر أربع طبقات ربط:
- Android — النواة مُجمَّعة إلى كود أصلي ومربوطة للغتي Kotlin/Java، وتُشحن بصيغة AAR.
- iOS / macOS — مسار gomobile نفسه، مربوطاً للغة Swift، ويُشحن بصيغة xcframework.
- cgo (Windows وLinux) — النواة بوصفها مكتبة c-shared خلف واجهة C ABI، وفوقها ترويسة C++17. وأي لغة لها FFI تصل إليها من هنا.
- JavaScript (wasm/web) — النواة مُجمَّعة إلى WebAssembly وتعمل في الصفحة، ويقودها إطار المتصفح الذي تستخدمه.
وهي كلها واجهاتٌ لنواة واحدة، فيتطابق السلوك عبر المنصات — لكنها ليست قابلة للتبادل، والفروق بينها تحسم بنيتك لا صياغتك وحدها. اقرأ أيُّ طبقة ربط تختار قبل أن تختار واحدة، وجولة SDK لمعرفة كيف بُنيت كلٌّ منها.
تستخدم URnetwork أجهزة مخارج يشغّلها الأعضاء. ولا يتلقى المزوّدون عناوين IP المصدرية لمستخدميك على المسارات المُرحَّلة. كما تستطيع الأجهزة المبنية على SDK أن تشفّر حركة البيانات حتى المزوّد، غير أن ذلك لا يحدث إلا بعد أن تفعّل PostQuantumEncryption؛ فالجهاز الجديد يبدأ وهذا العَلَم متوقف. اقرأ كيف تعمل URnetwork للاطلاع على النموذج كاملاً.
ما تحتاج إليه
الطبقات الأربع كلها واجهةٌ للنواة نفسها التي بُنيت عليها تطبيقات URnetwork الرسمية: الجهاز (محرك الاتصال)، وعميل API، ونموذج إعداد فضاء الشبكة (network space) — أي نشرة المنصة التي تخاطبها، وبأي نقاط نهاية وأعلام — ومتحكّمات العرض (view controllers)، وهي كائنات بلا واجهة تحمل الحالة والأحداث للشاشات الشائعة، تربط بها واجهتك أو تتجاهلها. وواجهة API الخاصة بالمشغّل خلف ذلك موثّقة في /docs/api. أنت تبني تطبيقك أنت على منصة URnetwork المستضافة: فواجهة API والمرحّلات والمزوّدون هم الخدمة الحية، ومستخدموك يجلبون حسابات أو ينشئونها.
الحدود الدنيا للمنصات، لكل طبقة ربط:
- Android — مستوى API 24 فأحدث. وحزمة Java هي
com.bringyour.sdk. - iOS / macOS — iOS 16.0 وmacOS 13.5.
- cgo — Ubuntu 22.04+ (glibc 2.35+) أو Windows 10+، على amd64 وarm64.
- JavaScript — للمتصفح وحده.
- البناء من المصدر — Go 1.26+.
ترخيص SDK هو MPL-2.0 (Mozilla Public License): اربطها بحرية داخل تطبيقات مغلقة المصدر. فالحقوق المتروكة على مستوى الملف لا تُلزم إلا بمشاركة التغييرات على ملفات SDK نفسها.
أيُّ طبقة ربط تختار
تستطيع طبقاتُ الربط الأصلية أن تملك مسارَ حزم الجهاز. أما صفحة المتصفح فلا تستطيع أن تملك واجهة TUN لنظام التشغيل؛ فكائنُ Device الخاص بها في JavaScript يتحكم في جهاز بعيد. وتكشف SDK المحدَّثة لـJS أيضاً مقابس للتطبيقات عبر كائن Device بعيد يدعم المقابس، ومنها TCP وUDP وTLS/DTLS وواجهة Direct Sockets في JavaScript. وهذا يوجّه اتصالات التطبيقات تلك من دون أن يجعل صفحة المتصفح شبكة VPN لنظام التشغيل.
| Android | iOS / macOS | cgo | JavaScript | |
|---|---|---|---|---|
| ما تكتبه | Kotlin / Java | Swift | C++ (أو أي FFI) | JS / TypeScript |
| تصل النواة بوصفها | كود أصلي مربوط بـgomobile | كود أصلي مربوط بـgomobile | مكتبة c-shared بواجهة C ABI | wasm في الصفحة |
| تشغّل مسار الحزم | نعم، في عملية تطبيقك | نعم، في NetworkExtension | نعم، في خدمة خفية تكتبها أنت | لا |
| نموذج العمليات | عملية واحدة | التطبيق + الامتداد، وmTLS عبر الاسترجاع المحلي | الواجهة + خدمة بصلاحيات مرتفعة، وmTLS عبر الاسترجاع المحلي | الصفحة وحدها |
| المُخرَج | نحو 37 MB بصيغة AAR | نحو 116 MB بصيغة xcframework | مكتبة بنحو 21–25 MB | نحو 43.5 MB من wasm |
وهذا ما يكلّفك إياه ذلك، مرتَّباً بحسب ما سيعضّك أولاً:
- Android هي الأبسط: عمليةٌ واحدة تحمل الواجهة و
VpnService، فيملك تطبيقكDeviceLocalمباشرةً، ويضخّIoLoopواصفَ ملف tun داخل Go بلا عبور JNI لكل حزمة. - iOS / macOS هي طبقة الربط نفسها بشكل أصعب. فكل VPN من Apple يعالج الحزم في عملية منفصلة معزولة، فيعيش الجهاز في الامتداد ويقوده تطبيقك عن بُعد. خطّط لدورة حياة إعادة الارتباط ولحدّ ذاكرة الامتداد منذ اليوم الأول — فليست حالةً طرفية، إذ يعيد نظام التشغيل تشغيل تلك العملية روتينياً.
- cgo هي الأكثر قابليةً للنقل والأقل راحةً في الاستعمال. فواجهة ABI مقابضُ وJSON لا كائناتٌ ذات أنواع، وهذا ما يتيح لـRust وPython وC# الوصول إليها؛ وترويسة C++17 هي الموضع الوحيد الذي أُنفق فيه على الراحة. وعليك أيضاً أن تكتب الخدمة الخفية ذات الصلاحيات بنفسك.
- JavaScript تقايض مسارَ الحزم بمدى الوصول. فتحصل على سطح API، ومتحكّمات العرض، و
DeviceRemoteفي مقابل جهاز مستضاف في مكان آخر — ومعها حمولة بحجم 43 MB عليك تحميلها بتكاسل.
والخيوط هي الموضع الذي تلتقي فيه الأربع على قاعدة واحدة: الكائنات غير الخاصة بالعرض آمنة للتزامن، ومتحكّمات العرض ليست كذلك. ويحصل JS في المتصفح على ذلك مجاناً؛ أما الثلاث الأخرى فلا. انظر الجولة للتفاصيل.
تسجيل الدخول
كل مقتطف أدناه يأخذ byJwt: وهو رمز JWT للحساب، أي رمز تسجيل دخول موقّع تُصدره واجهة API في URnetwork عند مصادقة حساب. ولا يوجد نظام مفاتيح API منفصل.
يشغّل تطبيقك مسار تسجيل دخول مرة واحدة عبر عميل API في SDK. يُبلغ authLogin عن طرق المصادقة المتاحة لمعرِّف ما؛ ويُتمّه authLoginWithPassword (ويُنهي authVerify رمزاً مرسلاً بالبريد أو بالرسائل القصيرة)، ومصادقةُ المحفظة وnetworkCreate مدخلان موازيان. ومصادقة المحفظة بالتوقيع وحده: ترسل wallet_address وwallet_message وwallet_signature، ولا ترسل مفتاحاً أبداً، ولا يطلب أي سطح في URnetwork المفتاح الخاص لمحفظة ولا عبارتها التذكيرية. أما networkCreate بلا أي طريقة مصادقة فهو مسار الحساب الفوري: يسكّ حساباً دائماً ويعيد seedphrase، وهي عبارة الاستعادة الخاصة بشبكة URnetwork لذلك الحساب، تُولَّد على الخادم وتُسلَّم مرة واحدة بالضبط. اعرضها على المستخدم حينها، وإلا فلا مسار استعادة للحساب.
وكل مسار يعيد by_jwt. احفظه، ونادِ setByJwt، ومرّره إلى مُنشئ الجهاز. ولا تكتب أي كود تجديد؛ فمدير الرموز في Api يدوّر رمز JWT. وعامِل إخفاق المصادقة القاطع بوصفه «شغّل تسجيل الدخول من جديد».
والحساب هو أيضاً وحدة الفوترة: فالحساب الذي يحمل الجهازُ رمزَ JWT الخاص به هو الحساب الذي تُقاس خطته. المجانية حصة بيانات يومية، وPro حصة شهرية كبيرة؛ والأرقام الحالية على ur.io/products. واقصد حساباً واحداً لكل مستخدم نهائي، وهو ما يجعله مسارُ الحساب الفوري بلا احتكاك، بدل حساب واحد مضمَّن يجمع استخدام كل مستخدم وسلوكه في فاعل واحد. وحين تنفد بيانات حساب، يتوقف النقل حتى تتجدد الحصة، فقل «نفدت البيانات» في تجربة المستخدم لديك، لا خطأ شبكة عاماً.
وتملك SDK أيضاً مسار الخطة المدفوعة، لأن العميل يجب ألا يسمّي سعره بنفسه أبداً: سجّل نيّة بـcreateSolanaPaymentIntent (مع reference من createPaymentReference، وplan بقيمة "monthly" أو "yearly")، وخذ amountUsd من النتيجة، ومرّرها إلى buildSolanaPaymentUrl. وفي واجهة C ABI استدعاءُ النيّة، أما بانِي الرابط فليس فيها بعد.
التثبيت
المخرجات المبنية مسبقاً موجودة بوصفها أصول إصدار على github.com/urnetwork/build. والإصدارات مؤرَّخة بالتاريخ، vYYYY.M.D- (مثل v2026.7.22-999364023)، لا بترقيم SemVer: ثبّت إصداراً بعينه وارتقِ عن قصد، لأن الرقم يقول متى قُطع الإصدار، لا ما إذا كانت واجهة API قد تحركت.
Android. نزّل URnetworkSdk-.aar (مع -sources.jar للتنقل في بيئة التطوير) وضعهما في مجلد تمسحه وحدةُ Gradle لديك أصلاً، مثل:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
}iOS / macOS. نزّل URnetworkSdk.xcframework.zip، وفُكَّ ضغطه، وأشِر إليه من حزمة Swift محلية بوصفه binaryTarget:
// Package.swift
targets: [
.binaryTarget(name: "URnetworkSdk", path: "URnetworkSdk.xcframework")
]وكل الأنواع مسبوقة بـSdk (SdkDeviceLocal وSdkNetworkSpace وغيرها). ويحمل xcframework الشرائح ios/arm64 وiossimulator/arm64 وmacos/arm64 وmacos/amd64؛ ولا توجد شريحة محاكي iOS لمعالجات Intel.
cgo (Windows وLinux). مكتبات c-shared، وهي الطبقة نفسها التي بُنيت عليها تطبيقات URnetwork المشحونة على Linux وWindows، ومعها ترويستان منتقاتان:
libURnetworkSdk.so— لنظام Linux.URnetworkSdk.dll(+urnetwork_sdk.def) — لنظام Windows.urnetwork_sdk.h— واجهة C ABI المجرّدة.urnetwork_sdk.hpp— غلاف C++17 بنمط RAII من ترويسات فقط فوقها، يتطلب nlohmann/json في مسار التضمين لديك.
وكل ما له واجهة استدعاء دوال خارجية بلغة C (مثل Rust bindgen وPython ctypes وC# P/Invoke) يتوافق مع واجهة ABI هذه القائمة على المقابض وJSON؛ وترويسة C++ هي التسهيل اللغوي الوحيد. ومع MSVC، ولّد مكتبة الاستيراد من ملف .def أولاً:
lib /def:urnetwork_sdk.def /machine:x64 /out:URnetworkSdk.libJavaScript (المتصفح وNode). الحزمة المعتمدة هي @urnetwork/sdk، وتحتوي المُحمِّل، وتصريحات TypeScript، وwasm_exec.js من Go، وملف WASM المطابق من SDK. استخدم إصداراً يدعم المقابس.
npm install @urnetwork/sdk@nightly # then pin the version it resolved toويحتوي ملف WASM نواةَ الشبكات في Go، وهو تنزيلٌ كبير. قدّمه أصلاً ساكناً (بضغط gzip أو brotli، مع تخزين مؤقت طويل) وحمّله بتكاسل، فقط حين يبلغ المستخدم سطحَ اتصال. ولا تدع أداةَ تجميع تُضمّنه أو تحوّله أبداً، واشحن دائماً sdk.wasm وwasm_exec.js من البناء نفسه؛ فالغراء مقترن بواجهة ABI مع سلسلة أدوات Go التي جمّعت wasm، وبناءُ SDK يشترط تطابق الاثنين بايتاً ببايت.
من المصدر. تحتاج إلى Go 1.26+، وللمنصات المحمولة نفّذ make init أولاً: فهو يثبّت نسخة gomobile نفسها التي تُقطع بها الروابط، ويثبّت أداة checksec التي يشغّلها هدف Android. ويحتاج build_android أيضاً إلى ضبط ANDROID_NDK_HOME، لأنه يجرّد .comment بأداة llvm-objcopy من NDK. ويغلّف sdk/build-android.sh وsdk/build-ios.sh الاثنين. ومن sdk/build:
make init # pinned gomobile + checksec; run before the mobile targets
make build_android # AAR
make build_apple # xcframework (build_ios is an alias)
make build_js # wasm + loader
make build_linux # c-shared .so, cross-compiled with zig
make build_windows # c-shared .dll; not in `all` — the shipped one builds in a VMويشترط build_android قائمةَ التخطي في gomobile، فالرمز المفقود يعني عدم تطابق إصدارات لا إسقاطَ ربط صامتاً؛ وgomobile لا يربط شرائح البنى (فالقوائم تعبر بوصفها SdkStringList وSdkIdList وغيرها) ولا يربط context.Context.
التقييمات الأمنية الخارجية، وحدودها، مغطّاة في نموذج التهديد.
إذن النفق
موافقةُ نظام التشغيل، والنفق نفسه، ملكٌ لتطبيقك أنت. وتبدأ SDK عند طبقة الحزم:
- على Android، خدمةُ
VpnServiceملكك: تطبيقك يعلنها، ويحصل على موافقة VPN بـVpnService.prepare()، ثم يناديestablish(). وتتسلّم SDK الأمر عند واصف الملف ذاك. - وعلى منصات Apple، القسمة اشتراطٌ من Apple لا من SDK: فأنفاق الحزم تعمل داخل NetworkExtension منفصل بحدود ذاكرة ضيقة، ولذلك يملك موفّر النفق (tunnel provider) الجهازَ بينما ترتبط به عمليةُ تطبيقك عن بُعد.
- وفي المتصفح، لا تستطيع صفحة أن تملك واجهة شبكة، فلا إذن يُطلب، ولا شيء في طبقة JavaScript ينفق حركةَ الصفحة نفسها. وكلا نموذجَي الجهاز في JavaScript يقود بدلاً من ذلك جهازاً على المنصة. ويغطي امتداد URnetwork تبويبات المتصفح؛ ويغطي تطبيق أصلي الجهازَ كله.
ولمعرفة ما تسجّله URnetwork عن الاتصالات، اقرأ نموذج التهديد.
الاتصال
يبدأ الجهاز الجديد على افتراضيات الشبكة، وهذه تترك الجلسة بين العميل والمزوّد غير مختومة. فلا ملف أداء للجهاز حتى تضبط له واحداً، ولذلك يبدأ PostQuantumEncryption متوقفاً، وهو العَلَم الذي يقف خلف عنصر التحكم «تشفير ما بعد الكم» (Post Quantum Encryption) في التطبيقات، ولا يفتح العميل أي جلسة مع المزوّد: فتسلك الحركة المسار المُرحَّل القياسي، حيث يستطيع المشغّل قراءة الحزم التي يُرحّلها. ولكي تختم الجلسة، اضبط العَلَم عبر setPerformanceProfile. وكل إصدارات المزوّد الحالية تفعّل الجانب المجيب، وما دام العَلَم مفعّلاً يعمل العميل بالإغلاق عند الفشل: فهو لا يحمل بيانات التطبيقات في العلن، ويتخطّى المزوّد الذي يتعذّر ختم جلسة معه بدلاً من استخدامه من دون ختم. وAllowDirect متوقف افتراضياً؛ وهو إعداد السرعة الذي تختاره بنفسك، ويزيل القفزة المُخفية للهوية ويسلّم ذلك المزوّدَ عنوان IP الحقيقي لمستخدمك، وتعرضه التطبيقات مقلوباً باسم «الإخفاء القوي للهوية» (Strong Anonymization)، وملفات الأجهزة المستضافة تُجبره على الإيقاف. أما الحماية من التسريب فعليك أن توصّلها بنفسك: مفتاح الإيقاف (kill switch) هو العنصر الأولي setRouteLocal، والجهاز يبدأ والتوجيه المحلي مسموح، وsetRouteLocal(false) يجعل الحركة تتوقف بدل أن ترتد حين يسقط النفق. ولا تضمّن SDK أي تحليلات ولا تبليغ أعطال؛ واتصالاتها الوحيدة هي نقاط نهاية المنصة والمرحّلات والمزوّدون الذين يستخدمهم الجهاز.
Android
ترتيب الإقلاع: أنشئ NetworkSpaceManager موجَّهاً إلى تخزين خاص بالتطبيق (فالمجلد يحمل الحالة المحلية، ومنها الاعتمادات)، ثم أنشئ فضاء الشبكة، ثم اضبط رمز JWT للحساب على عميل API الخاص به. والمفتاح اسمُ مضيف مع اسم بيئة؛ والإنتاج هو ("ur.network", "main")، وupdateNetworkSpace هو ما يُنشئ فضاءً، بينما getNetworkSpace لا يقرأ إلا فضاءً موجوداً، فيعيد null على تثبيت جديد. وتُشتق العناوين من المفتاح (https://api. وwss://connect.؛ والبيئة غير main تسبق اسم الخدمة ببادئة)، لكن الاشتقاق يفضّل migrationHostName، الذي يضبطه الإنتاج على bringyour.com: فالتطبيقات المشحونة تصل إلى api.bringyour.com، وapi.ur.network لا يُحلّ. ثم أنشئ الجهاز وسلّمه واصفَ ملف النفق:
import com.bringyour.sdk.Sdk
val manager = Sdk.newNetworkSpaceManager(context.filesDir.absolutePath)
val key = Sdk.newNetworkSpaceKey("ur.network", "main")
val networkSpace = manager.updateNetworkSpace(key) { it.migrationHostName = "bringyour.com" }
networkSpace.api.setByJwt(byJwt)
val device = Sdk.newDeviceLocalWithMemoryTarget(
networkSpace,
byJwt,
deviceDescription, // free-form, shown in the account's device list
deviceSpec, // e.g. Build.MODEL
appVersion,
Sdk.newId(), // instanceId; persist and reuse per install
/* enableRpc */ false,
keyMaterial, // persisted DeviceLocalKeyMaterial, or null for ephemeral
memoryTargetByteCount,
)
// inside your VpnService, after establish():
val detachedFd = pfd.detachFd() // ParcelFileDescriptor -> raw fd
val ioLoop = Sdk.newIoLoop(device, detachedFd) { /* done callback */ }يتملّك newIoLoop واصفَ الملف المفصول ويضخّ الحزم في الاتجاهين حتى تغلقه. ولا تلمس واصف الملف من Java بعد فصله.
ثلاثة من وسائط المُنشئ تستحق العناية. instanceId: ولّد واحداً بـSdk.newId() عند أول تشغيل، واحفظه، وأعد استخدامه طوال عمر التثبيت (جهاز حيّ واحد لكل عملية)؛ فمعرّف جديد مع كل إطلاق يضيف مدخلاً شبحياً إلى قائمة أجهزة الحساب. وkeyMaterial: لا بأس بـnull لعميل محض، لكن إن كان الجهاز سيزوّد سعةً يوماً، فاحفظ getKeyMaterial() في التخزين الآمن للمنصة. فهي هوية الجهاز بوصفه مزوّداً، وفقدانها يصفّر تاريخ موثوقية المزوّد. وmemoryTargetByteCount: ميزانية بايتات يقيس عليها الجهاز أحجام مخازنه المؤقتة وإيقاع جمع المهملات؛ اختر رقماً يناسب السقف الحقيقي لعمليتك (انظر الجولة). وDeviceLocal داخل العملية: فإن ماتت عمليتك مات النفق معها، فشغّل VpnService بوصفها خدمة أمامية، وأعد إنشاء الجهاز عند إعادة التشغيل بالمعرّف instanceId نفسه وبمادة المفاتيح نفسها.
iOS / macOS
يملك NEPacketTunnelProvider كائنَ SdkDeviceLocal وتدفقَ الحزم، بينما ترتبط به عمليةُ التطبيق بوصفها جهازاً بعيداً عبر الاسترجاع المحلي (loopback):
// in the packet tunnel provider (owns the device):
var err: NSError?
let device = SdkNewDeviceLocalWithMemoryTarget(
networkSpace, byJwt, deviceDescription, deviceSpec, appVersion,
instanceId, /* enableRpc */ true, keyMaterial, memoryTarget, &err)
// in the app process (attaches to it):
let remote = SdkNewDeviceRemoteWithDefaults(networkSpace, byJwt, instanceId, &err)
try remote?.setRpcServer(clientPem, serverCertPem: serverCertPem, hostPort: hostPort)يسكّ التطبيق مادةَ المفاتيح وملفات PEM الخاصة بـRPC (عبر SdkGenerateDeviceRpcKeyMaterial) ويسلّمها إلى الامتداد في إعداد الموفّر NETunnelProviderProtocol؛ ويقرأ الامتداد من هناك rpc_server_pem وrpc_client_pem وrpc_listen_hostport. ولا توجد مجموعة تطبيقات (app group) على هذا المسار. انظر الجولة لمعرفة سبب وجود القسمة وكيف تعمل إعادة الاتصال.
cgo (Windows وLinux)
عقد ABI باختصار:
- الكائنات مقابض
uint64_tمبهمة. ويحرّرurnet_release(h)المقبض من دون إيقاف الكائن، فنادِ أولاً*_close/*_stopالخاص به حيث يوجد. - سلاسل
char*المعادة ملكٌ للمستدعي؛ حرّرها بـurnet_free_string. - تعبر البياناتُ المهيكلة الحدَّ بوصفها سلاسل JSON بترميز UTF-8؛ والمعرّفات سلاسل UUID، والأوقات بالمللي ثانية منذ حقبة Unix (و
0تعني لا شيء). - تُطلَق ردود النداء على خيوط عشوائية تديرها Go؛ فانقلها إلى خيطك أنت. وسلاسلها ومخازنها المؤقتة لا تعيش إلا مدة النداء، والمقابض التي تسلّمها لك عليك أنت أن تحرّرها.
- الاستدعاءات القابلة للإخفاق تأخذ
char** out_error؛ وعند الإخفاق يُضبط على رسالة تحرّرها بـurnet_free_string. ومرّرNULLلتجاهل النص.
وتحمل الترويسة انقساماً واحداً بحسب المنصة: فـurnet_new_io_loop، وهي مضخّة واصف الملف التي يستخدمها تطبيق Linux، تقع داخل #if !defined(_WIN32) وتغيب عن ملف .def الخاص بـWindows. وعلى Windows تحرّك الحزم بـurnet_device_local_send_packet وurnet_device_local_add_receive_packet.
ويسكن مثال عامل من طرف إلى طرف في cgo/smoke؛ ويبني make smoke_hpp في sdk/cgo اختبارَ الدخان (smoke test) لغلاف C++ مقابل بناء مضيف ثم يشغّله.
JavaScript (wasm/web)
import { URNetwork } from "@urnetwork/sdk";
const sdk = await URNetwork.init({
wasmUrl: "/wasm/sdk.wasm",
wasmExecUrl: "/wasm/wasm_exec.js",
});يسجّل wasm صادراته على globalThis، فشغّل نسخة وحدة واحدة لكل صفحة. وinit عديم الأثر عند التكرار داخل الوحدة (فالنداء الثاني يعيد النسخة نفسها)، لكن نسختين من المُحمِّل — حزمتين مثلاً أو إطارين يتشاركان مجالاً واحداً — تتسابقان على المتغيرات العامة نفسها. هيّئه مرة واحدة في مفرد على مستوى الوحدة، لا داخل دورة حياة مكوّن أبداً.
وتستخدم أمثلةُ المقابس sdk.createPlatformDeviceRemote({...})، وهو كائن DeviceRemote يتكلم device-RPC مع جهاز Device مستضاف عبر WebSocket. احصل على إعداد الاتصال وعلى معرّف النسخة الفعلي لذلك الجهاز المستضاف من خدمة الاستضافة لديك:
const device = sdk.createPlatformDeviceRemote({
apiUrl: "api.bringyour.com",
platformUrl: "connect.bringyour.com",
byJwt, proxyUrl,
signedProxyId, // HMAC auth token, not the JWT — see the tour
instanceId, // the actual hosted Device instance, not a fresh UUID
});
const unsub = device.addConnectLocationChangeListener((loc) => {
console.log("location:", loc?.name);
});
device.setConnectLocation({ bestAvailable: true });ويرمي createPlatformDeviceRemote استثناءً إن كان ملف wasm المحمَّل أقدم من ربط DeviceRemote؛ وذلك عَرَضُ وسم npm قديم.
تأكّد من أنها تعمل
ارفع الجهاز وافحص المخرج: أي فحص من نوع «ما عنوان IP الخاص بي» عبر النفق ينبغي أن يُبلغ الآن عن عنوان مزوّد، لا عن عنوان الجهاز. والحالة نفسها مرئية في الكود وفي الحساب: فالمستمعون يُطلقون مع تغيّر الاتصال (ويسجّل مقتطف JavaScript أعلاه كل تغيّر موقع لحظة وقوعه)، ويظهر الجهاز في قائمة أجهزة الحساب تحت وصف deviceDescription الذي مرّرته. وعلى واجهة C ABI، يُبلغ urnet_live_handle_count() عن المقابض الحية؛ فتحقّق في اختبارات التسريب من أنه يعود إلى خط الأساس.
إذا أخفق شيء
الدعم هو القنوات المفتوحة: المشكلات على المستودعات (github.com/urnetwork)، وملاحظات المنتج على feedback.ur.io، والبلاغات الأمنية إلى [email protected] (وسياسة الإفصاح على ur.io/vdp). ولا توجد اليوم فئة دعم مدفوعة لـSDK. وهناك توقفان لهما تفسير سهل: توقف النقل على نفق يعمل يعني عادةً حساباً نفدت بياناته، ومُحمِّل JavaScript الذي لا شيء ليحمّله هو وسم npm الموسوم latest. وتغطي الجولة أنماط الإخفاق التي ينبغي أن تعرفها قبل أن تشحن.
ماذا بعد
- خُذ جولة SDK. البنية خلف كل طبقة ربط: انقسامات العمليات، والخيوط، والمصادقة، وإعادة الاتصال، وتحديد حجم هدف الذاكرة.
- أخبر مستخدميك بما ينضمّون إليه. إن كان تطبيقك يوجّه الحركة عبر URnetwork، فـالنظرة العامة هي الوصف المعتمد للمسار ولما يستطيع كل طرف رؤيته، ونموذج التهديد هو السجل الكامل خلفه.
- انظر التطبيقات المكتملة لترى التجربة التي سيحصل عليها مستخدموك: Android وiOS وmacOS وWindows وLinux والمتصفح.