فهرست مستندات
سرویسهای میزبان اپ و بافت مکانی#
Host Services تنها راه پشتیبانیشده برای خواندن بخش مجاز از بافت میزبان یا
درخواست یک عمل از محیط است. App هیچگاه کد Environment را import نمیکند، صحنه
یا کروم پلتفرم را نمیخواند و توکن Runtime یا Plugin Grant نمیگیرد.
App revision
createEmbeddedAppClient()
│ versioned App frames
▼
desktop AppHost session ─ capability grant / generation / deadline
│ validated Main↔Environment relay
▼
EnvironmentHostAdapter ── Environment-owned scene semantics
handshake اجرا یک کانال منطقی پایدار میسازد. در کلاینت دسکتاپ،
window.alamr.appHost آن را از WebContentsView sandboxشدهٔ App بیرون میآورد و
Main مالک AppHostProtocolSession و همان پروتکل frame نسخهدار SDK است. درخواست، پاسخ،
خطا، لغو، رویداد و lifecycle همگی روی همین کانال و برای یک revision و نسل دقیق
اجرا میشوند. هر frame حداکثر ۶۴ KiB است و App حداکثر هشت درخواست همزمان دارد.
کجا پیاده شده است#
packages/contracts/src/host-services.ts— شناسهٔ قابلیتها، شکل frameها و زمینهٔ مکانی که هر دو طرف این پروتکل با آن اعتبارسنجی میکنند.packages/sdk/src/embedded-app.ts— نیمهٔ اپ:createEmbeddedAppClient، frameهای درخواست و پاسخ و چرخهٔ عمر، و هر فراخوان میزبانی که اپ میتواند بزند.packages/sdk/src/app-client-port.ts— adapter کانال کهMessagePortسند یا bridge دسکتاپwindow.alamr.appHostرا انتخاب میکند.packages/sdk/src/app-host-protocol-session.ts— نشست منطقی و مستقل از framework میزبان برای launch، renewal، حصار نسل، dispatch محدود، presentation وbeforeExit. مرورگر و Desktop هر دو port احرازشده را به همین implementation میدهند.packages/sdk/src/host-service-dispatcher.tsوpackages/sdk/src/environment-host.ts— نیمهٔ محیط، جایی کهEnvironmentHostAdapterپاسخ میدهد.packages/contracts/src/environment-bridge.ts،packages/bridgeوpackages/sdk/src/environment-host-service-relay.ts— رلهٔ نسخهدار و محدود به ۶۴ KiB در Desktop. Main فقط متدهای متعلق به Environment را پس از پذیرش policy میفرستد؛ Environment نتیجههای schema-checked و تغییر کامل context مکانی را بدون عبور function بین processها برمیگرداند.packages/react/src/app-frame.tsx— adapter پذیرش مرورگر؛ source/origin iframe را میسنجد و portهای browser-only برای user activation، تب consent و companion picker را تزریق میکند، اما مالک launch یا renewal نیست.platform/desktop/src/main/apps/app-view-manager.ts— adapter دسکتاپِ مقید به receipt که برای هر سند مستقل App یکAppHostProtocolSessionمشترک میسازد و آن را به سرویسهای میزبانِ Main-owned متصل میکند.platform/desktop/src/main/apps/environment-host-adapter-provider.ts— proxy context-aware در Main برای Environment نصبشدهٔ فعلی. Home، یک Environment دیگر، سند جایگزینشده و surface آزادشده همگی fail-closed هستند و adapter صحنهٔ کهنه را قرض نمیگیرند.
قابلیتها#
اپ قابلیتهای بسته و نسخهبندیشده را در al-amr.app.json درخواست میکند:
{
"capabilities": {
"required": [
{
"id": "platform.environment.identity.read@1",
"versionRange": "^1.0.0"
},
{
"id": "host.spatial.context.read@1",
"versionRange": "^1.0.0"
}
],
"optional": [
{
"id": "host.spatial.locations.read@1",
"versionRange": "^1.0.0"
}
]
}
}
نبودن قابلیت required جلوی اجرای اپ را میگیرد. نبودن قابلیت optional باید
به تجربهای کوچکتر ولی سالم منجر شود. وابستگی هر قابلیت باید در همان سطح یا
سطح قویتر اعلام شود. رجیستری پیش از اجرای App، نسخهٔ منتشرشده، کاتالوگ
قابلیت، نوع کاربر، وابستگی، Runtime و رضایت کاربر را بررسی میکند. درخواست
مانیفست به معنی مجوز نیست؛ اپ باید
client.snapshot.hostSession.grantedCapabilities را بررسی کند.
| قابلیت | افشا و فعالسازی |
|---|---|
platform.identity.profile.read@1 | نام و آواتار حساب؛ فقط حساب |
platform.environment.identity.read@1 | هویت امضاشدهٔ محیط فعال |
host.spatial.context.read@1 | زون و موقعیت معنایی فعلی |
host.spatial.locations.read@1 | موقعیتهای پایدار اعلامشده توسط محیط |
host.spatial.location.capture@1 | مرجع opaque برای جای فعلی |
host.spatial.navigation.request@1 | درخواست حرکت از محیط |
platform.notifications.publish.self@1 | signal در declaration بازبینیشدهٔ اعلان برای principal فعلی App |
platform.notifications.connect.background.self@1 | واگذاری اعلان پسزمینه به بکاند اپ؛ فقط حساب |
platform.companions.list@1 | خواندنهای بعدی نام، چهره و شناسهٔ ناشناسِ مخصوص اپ تا زمان قطع؛ بدون موقعیت؛ فقط حساب |
platform.companions.offer@1 | پیشنهاد دادن به یکی از همراهان، بدون اینکه اپ بداند او کیست |
platform.notify.companion@1 | یک اعلان پلتفرمیِ بدون محتوای اپ برای یک همراه |
platform.call@1 | برقراری تماس با یک همراه و دریافت تماسهای او؛ فقط حساب؛ زنگ بهشکل CallAlert زندهٔ حساب میرسد |
platform.media.microphone@1 | اجازهٔ درخواست میکروفن به قاب همان اپ |
platform.presence.roster.read@1 | چند نفر در این دنیا هستند؛ هرگز اینکه چه کسانی |
فعالسازی کاربر خاصیت متد است نه قابلیت، و جدول بالا عمداً دیگر خلافش را
ادعا نمیکند. HOST_SERVICE_METHOD_DEFINITIONS مقدار requiresUserActivation
را به ازای هر متد نگه میدارد، و این تنها دانهبندیای است که میتواند قاعدهٔ
واقعی را بیان کند: host.spatial.navigation.request@1 برای assessNavigation
ژست میخواهد و برای commitNavigation نمیخواهد، چون ژست متعلق به لحظهای است
که شخص درخواست کرده، نه لحظهای که میزبان بر اساسش عمل میکند.
دسترسی به همراه در بکاند خود اپ#
platform.companions.list@1 فقط شناسهٔ جفتیِ aact_ مخصوص همان اپ و نمایش
حلشده را میدهد؛ نه شناسهٔ حساب، نه محل حضور، نه محیط و نه کلید خام گراف
همراهی. خودِ شکل aact_ اثبات نمیکند رابطه هنوز برقرار است.
پیش از یک نوشتن مستقیم برای شخص دیگر، اپ یکی از subjectهای همان فهرست رضایتداده
را به POST /v1/apps/companions/reach میدهد. رجیستری یال زنده، امتناعها و
تعلیق را دوباره میسنجد و در صورت مجازبودن، اثبات امضاشدهای با عمر ۶۰ ثانیه
میدهد. همهٔ ردهای مربوط به طرف مقابل فقط
{ "status": "unavailable" } هستند. بکاند خود اپ با
createAppCompanionReachVerifier صادرکننده، App، تماسگیرنده و همراه دقیق را
بررسی میکند و هرگز subject را فقط از روی شکلش قبول نمیکند.
این قابلیت عمومیِ همهٔ اپهاست، نه API پیامرسان. هیچ محتوا یا شناسهٔ گفتوگویی از رجیستری عبور نمیکند و دادهٔ محصول طبق ADR-0068 در بکاند خود اپ میماند.
اعلام محیط#
محیطی که سرویس مکانی ارائه میدهد باید واژگان پایدار و سرویس متناظر را با هم اعلام کند:
{
"spatial": {
"semanticLocations": [
{
"locationId": "garden-gate",
"label": "Garden gate",
"navigationPolicy": "confirm"
}
]
},
"hostServices": {
"protocolVersion": "1.0.0",
"provides": [
"host.spatial.context@1",
"host.spatial.locations@1",
"host.spatial.capture@1",
"host.spatial.navigation@1"
]
}
}
شناسهٔ موقعیت، معنای متعلق به محیط است؛ route یا مختصات نیست. اگر دادهٔ اپ به آن اشاره میکند، شناسه را پایدار نگه دارید. انتشار، snapshot تغییرناپذیر این واژگان را میسازد.
نبودِ زون در این اعلام عمدی است. محیط زونِ فعلی خود را در زمان اجرا روی
EnvironmentSpatialContext میگوید، با همان برچسبی که بازدیدکنندهاش میخواند؛
و محیطی که یک فضای پیوسته است هیچ زونی نمیفرستد — پس رجیستری هرگز فهرست زون
نگه نمیدارد. استدلالش در ADR-0075 است.
Adapter را از state واقعی محیط بسازید و به ویجت بدهید:
import { createSemanticEnvironmentHostAdapter } from "@al-amr/sdk";
const hostAdapter = createSemanticEnvironmentHostAdapter({
environmentId: manifest.environmentId,
environmentRevisionId: () => currentRevisionId,
spatial: manifest.spatial!,
currentZoneId: () => currentZoneId,
currentSemanticLocationId: () => currentLocationId,
capturedLocationResolver: {
capture: () => ({
kind: "captured",
environmentId: manifest.environmentId,
environmentRevisionId: currentRevisionId,
zoneId: currentZoneId,
resolverId: "example.player-position",
resolverVersion: "1.0.0",
locator: worldNavigation.captureOpaqueLocator(),
}),
assess: (target) =>
worldNavigation.canResolveCapturedPlace(target.locator)
? {
presentation: { title: "Saved place" },
minimumPolicy: "direct",
}
: undefined,
navigate: (target) => worldNavigation.goToCapturedPlace(target.locator),
},
navigate: async (locationId) => {
return await worldNavigation.goToSemanticLocation(locationId);
},
onWidgetStateChange: ({ capturesInput, occupiedRect }) => {
controls.setWidgetCapture(capturesInput);
camera.setWidgetSafeArea(occupiedRect);
},
});
<AlAmrWidget hostAdapter={hostAdapter} />
وقتی preload دسکتاپ یا development host رلهٔ
window.alamr.hostServices را فراهم کند، AlAmrWidget این adapter را خودکار
به آن متصل میکند و هنگام unmount هم subscription درخواست و هم context مکانی را
آزاد میکند. Environment بدون React میتواند همین اتصال را صریح بسازد:
bindEnvironmentHostServices(window.alamr.hostServices, hostAdapter).
این hop اضافه authority را به renderer محیط منتقل نمیکند. Main پیش از relay هر متد متعلق به Environment همچنان capability اعطاشده توسط Registry، نسل فعلی App، context محیط فعلی، user activation، بودجهٔ درخواست، navigation assessment و deadline را بررسی میکند. پاسخ باید دقیقاً با id، generation و method درخواست برابر باشد. reload کار درحالپرواز را stale میکند و Environment غایب یا آزادشده با خطای retryable unavailable پاسخ میگیرد.
پس از تغییر واقعی زون یا موقعیت معنایی،
hostAdapter.refreshSpatialContext() را صدا بزنید. Adapter یک visitId پایدار
و sequence صعودی را نگه میدارد. موقعیت را قبل از ورود واقعی گزارش نکنید.
برای پشتیبانی از نقطههای دلخواهی که موقعیت معنایی authored نیستند، کل
capturedLocationResolver شامل capture، assess و navigate را فراهم کنید.
خروجی آن locator محدود و opaque به همراه شناسه/نسخهٔ resolver و نسخهٔ دقیق
محیط است. در هر assess، locator را ورودی غیرقابلاعتماد بدانید و نسخه، زون،
ساختار، محدوده، دسترسی و قابلحرکتبودن آن را بررسی کنید. SDK پیش از navigate
در زمان commit دوباره assess را اجرا میکند تا capture و بازگشت دو مسیر امنیتی
جدا نباشند. اپ مرجع را ذخیره میکند ولی locator آن را تفسیر نمیکند.
استفاده در اپ#
برای هر نسل سند App یک بار متصل شوید:
import { createEmbeddedAppClient } from "@al-amr/sdk";
const client = createEmbeddedAppClient({
appId: manifest.appId,
registryBaseUrl,
});
const session = await client.connect();
const mode = session.displayMode;
const context = await client.getSpatialContext();
const locations = await client.listSemanticLocations();
وقتی اپ به affordance مکانی زنده نیاز دارد subscribe کنید:
const unsubscribe = client.subscribeSpatialContext((nextContext) => {
notes.showCurrentPlace(session.environmentId, nextContext.zoneId);
});
Capture و navigation باید از عمل مستقیم کاربر شروع شوند:
saveButton.addEventListener("click", async () => {
const location = await client.captureCurrentLocation();
await notes.save({ location });
});
goButton.addEventListener("click", async () => {
const assessment = await client.assessNavigation(note.location);
showAssessment(assessment); // The App may explain, but the host owns confirmation.
const result = await client.commitNavigation(assessment.assessmentId);
if (result.status === "unavailable") showUnavailable(result.reason);
});
Notes برای ساخت یا ویرایش یادداشت عادی به این capabilityهای مکانی نیاز ندارد.
کتابخانه و پوشههایش با actor جفتی اپ کلید میخورند. capture انتخاب صریح زمان
ساخت است که provenance تغییرناپذیر میافزاید؛ هیچگاه پوشه یا scope پیکرهٔ
یادداشت نمیشود. یادداشت پیوستشده به محیط دیگر همچنان بهعنوان context نمایش
داده میشود، اما تا وقتی session.environmentId با محیط پیوست برابر نیست Notes
نباید assessNavigation را ارائه کند.
Notes ویرایش آفلاین را فقط پس از پایدارشدن outbox همان actor تأیید میکند. پنجرههای هممبدأٔ اپ، تراکنشهای کوتاه cache را با Web Lock انحصاریِ actor سری میکنند، پس از گرفتن قفل دوباره مقدار پایدار را میخوانند و با رویدادهای storage همگرا میشوند. قفل دوم برای همان actor فقط یک worker همگامسازی راه دور را انتخاب میکند؛ fetch هیچگاه قفل cache را نگه نمیدارد، بنابراین شبکهٔ معطل نمیتواند ذخیرهٔ محلی پنجرهٔ دیگر را متوقف کند. اگر storage پایدار یا Web Locks موجود نباشد، Notes ادعای ذخیرهٔ محلی نمیکند. کار شبکه deadline و backoff نماییِ jitterدار دارد؛ teardown پذیرش mutation تازه را میبندد، mutationهای ازپیشپذیرفته را تا storage محلی drain میکند، transport را abort میکند و پاسخ دیررس را پیش از تغییر cache کنار میگذارد.
ارزیابی و commit عمداً جدا هستند. ارزیابی کوتاهعمر، متصل به caller و در commit دوباره بررسی میشود. اپ مستقیماً player، camera، route یا Zone را تغییر نمیدهد. commit مکانی موفق در همان محیط سند App را باز نگه میدارد؛ این حرکت داخل تجربهٔ فعلی است، نه transition چرخهٔ عمر App.
قواعد مرجع مکانی#
- مرجع
semanticشامل شناسهٔ محیط و موقعیت پایدار است و میتواند در deep link میان محیطها توسط رجیستری بررسی شود. - مرجع
capturedopaque و متصل به نسخه است و داخل URL اجرا قرار نمیگیرد. - مختصات، pose بازیکن، camera transform، route خصوصی و شناسهٔ node صحنه هویت مکانی قابلحمل نیستند.
- بافت مکانی raw account subject ندارد. دادهٔ اپ با actor جفتی و تأییدشدهٔ اپ کلید میخورد.
- بافت مکانی هویت محیط هم ندارد. محیط بافت را از manifest خودش پر میکند، پس
نسخهای از هویت در آنجا حرفِ خود محیط دربارهٔ خودش میبود — و بافت روی
host.spatial.context.read@1سوار است، یعنی هویت را به اپی میداد که کاربرشplatform.environment.identity.read@1را رد کرده. هویت محیط فقط ازsession.environmentIdمیآید که رجیستری از رکورد خودش امضا میکند و پشت همان قابلیت نگه میدارد.
Lifecycle#
فقط برای کار ذخیرهنشده handler بگذارید:
client.setBeforeExitHandler(async ({ reason }) => {
if (!draft.dirty) return { disposition: "allow" };
const saved = await draft.saveBeforeDeadline();
return saved
? { disposition: "allow" }
: { disposition: "confirm", message: exitMessage(reason) };
});
میزبان حداکثر یک ثانیه منتظر میماند و خطا نمیتواند کاربر را محبوس کند.
در cleanup، client.clear() را صدا بزنید.
beforeExit اکنون بهمراتب کمتر از گذشته اجرا میشود. تغییر Compact/Workspace
سند یا نشست تازه نمیسازد و آن را صدا نمیزند، و بستن پنل هم برای اپی که
display.background: "persistent" اعلام کرده آن را صدا نمیزند — آن اپ park
میشود، نه exit. همچنان برای بستن واقعی، برای replace وقتی سومین اپ در حال اجرا
باید جا باز کند، و برای از دست رفتن Runtime اجرا میشود.
دو رویداد و دو متد میزبان برای اپهای parkشده وجود دارد:
| فریم | جهت | معنا |
|---|---|---|
host.presentation.changed | میزبان ← اپ | presented یا background، حالت جاری، دیدهشدن صفحه، توانایی پاسخ میزبان و ساعت دیواری میزبان |
host.session.renewal.due | میزبان ← اپ | نشست پنجدقیقهای باید دوباره صادر شود |
host.presentation.get | اپ ← میزبان | presentation جاری، برای اپی که دیر مشترک شده |
host.presentation.audible | اپ ← میزبان | اطلاع: این اپ صدا تولید میکند |
host.session.renew | اپ ← میزبان | صدور کد launch تازه در برابر challenge و nonce تازه |
هیچکدام از این چهار قابلیتی نمیخواهد و هیچکدام ژست کاربر لازم ندارد: ژست، تمدید را برای اپی که پشت پنل بسته است — تنها اپی که به آن نیاز دارد — ذاتاً ناممکن میکرد. تمدید، مراسم launch را کامل تکرار میکند، پس هر بررسیای که رجیستری روی launch اول اجرا میکند دوباره اجرا میشود؛ به رجیستری هرگز گفته نمیشود که این فراخوانی یک تمدید است، و همین ندانستن، خودِ آن ویژگی است.
همچنین اپها، مرجع مانیفست، مرجع SDK و ADR-0046 را بخوانید.