@camstack/core 0.1.23 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"auth-orchestrator.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/auth-orchestrator/auth-orchestrator.addon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,SAAS,EAIT,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAA;AAYxB,qBAAa,qBAAsB,SAAQ,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;;cAKzD,YAAY,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;YAgBjD,aAAa;CAa5B;AAED,eAAe,qBAAqB,CAAA"}
1
+ {"version":3,"file":"auth-orchestrator.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/auth-orchestrator/auth-orchestrator.addon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,SAAS,EAIT,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAA;AAWxB,qBAAa,qBAAsB,SAAQ,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;;cAKzD,YAAY,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;YAgBjD,aAAa;CAY5B;AAED,eAAe,qBAAqB,CAAA"}
@@ -40,7 +40,6 @@ var AuthOrchestratorAddon = class extends _camstack_types.BaseAddon {
40
40
  return (this.capabilities?.getCollectionEntries("auth-provider") ?? []).map(([addonId, raw]) => ({
41
41
  addonId: raw?.addonId ?? addonId,
42
42
  displayName: raw?.displayName ?? addonId,
43
- kind: raw?.kind ?? (addonId === "local-auth" ? "local" : "other"),
44
43
  ...raw?.icon !== void 0 ? { icon: raw.icon } : {},
45
44
  hasRedirectFlow: raw?.hasRedirectFlow ?? false,
46
45
  hasCredentialFlow: raw?.hasCredentialFlow ?? addonId === "local-auth",
@@ -1 +1 @@
1
- {"version":3,"file":"auth-orchestrator.addon.js","names":[],"sources":["../../../src/builtins/auth-orchestrator/auth-orchestrator.addon.ts"],"sourcesContent":["/**\n * Authentication orchestrator — singleton facade over the\n * `auth-provider` collection. Mirrors the backup-orchestrator pattern:\n * UI consumers go through this builtin, which walks the\n * `auth-provider` collection registered by `local-auth` and any\n * additional `auth-*` addons (OIDC, SAML, LDAP, …) and returns a\n * curated view.\n *\n * The actual login flow still goes through `auth-provider`\n * collection methods (validateCredentials / getLoginUrl / …); this\n * cap exists so the admin UI's \"Authentication\" page and the login\n * screen's provider picker have ONE place to query instead of\n * walking the collection themselves.\n */\nimport {\n BaseAddon,\n authenticationCapability,\n type AuthProviderInfo,\n type IAuthenticationProvider,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface AuthProviderRegistrationLike {\n readonly addonId?: string\n readonly displayName?: string\n readonly kind?: AuthProviderInfo['kind']\n readonly icon?: string\n readonly hasRedirectFlow?: boolean\n readonly hasCredentialFlow?: boolean\n readonly status?: string\n}\n\nexport class AuthOrchestratorAddon extends BaseAddon<Record<string, never>> {\n constructor() {\n super({})\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: IAuthenticationProvider = {\n listProviders: async () => this.listProviders(),\n setProviderEnabled: async () => {\n // Persistence is per-addon today — operators toggle enabled\n // via the provider addon's own settings panel. The orchestrator\n // exposes the affordance for forward-compat (UI may move the\n // enabled toggle into the Authentication page later) but\n // currently no-ops the persistence side.\n return { success: true as const }\n },\n }\n this.ctx.logger.info('Authentication orchestrator initialized')\n return [{ capability: authenticationCapability, provider }]\n }\n\n private async listProviders(): Promise<readonly AuthProviderInfo[]> {\n const entries = this.capabilities?.getCollectionEntries<AuthProviderRegistrationLike>('auth-provider') ?? []\n return entries.map(([addonId, raw]) => ({\n addonId: raw?.addonId ?? addonId,\n displayName: raw?.displayName ?? addonId,\n kind: raw?.kind ?? (addonId === 'local-auth' ? ('local' as const) : ('other' as const)),\n ...(raw?.icon !== undefined ? { icon: raw.icon } : {}),\n hasRedirectFlow: raw?.hasRedirectFlow ?? false,\n hasCredentialFlow: raw?.hasCredentialFlow ?? (addonId === 'local-auth'),\n ...(raw?.status !== undefined ? { status: raw.status } : {}),\n enabled: true,\n }))\n }\n}\n\nexport default AuthOrchestratorAddon\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,wBAAb,cAA2C,gBAAA,UAAiC;CAC1E,cAAc;EACZ,MAAM,EAAE,CAAC;;CAGX,MAAgB,eAAgD;EAC9D,MAAM,WAAoC;GACxC,eAAe,YAAY,KAAK,eAAe;GAC/C,oBAAoB,YAAY;IAM9B,OAAO,EAAE,SAAS,MAAe;;GAEpC;EACD,KAAK,IAAI,OAAO,KAAK,0CAA0C;EAC/D,OAAO,CAAC;GAAE,YAAY,gBAAA;GAA0B;GAAU,CAAC;;CAG7D,MAAc,gBAAsD;EAElE,QADgB,KAAK,cAAc,qBAAmD,gBAAgB,IAAI,EAAE,EAC7F,KAAK,CAAC,SAAS,UAAU;GACtC,SAAS,KAAK,WAAW;GACzB,aAAa,KAAK,eAAe;GACjC,MAAM,KAAK,SAAS,YAAY,eAAgB,UAAqB;GACrE,GAAI,KAAK,SAAS,KAAA,IAAY,EAAE,MAAM,IAAI,MAAM,GAAG,EAAE;GACrD,iBAAiB,KAAK,mBAAmB;GACzC,mBAAmB,KAAK,qBAAsB,YAAY;GAC1D,GAAI,KAAK,WAAW,KAAA,IAAY,EAAE,QAAQ,IAAI,QAAQ,GAAG,EAAE;GAC3D,SAAS;GACV,EAAE"}
1
+ {"version":3,"file":"auth-orchestrator.addon.js","names":[],"sources":["../../../src/builtins/auth-orchestrator/auth-orchestrator.addon.ts"],"sourcesContent":["/**\n * Authentication orchestrator — singleton facade over the\n * `auth-provider` collection. Mirrors the backup-orchestrator pattern:\n * UI consumers go through this builtin, which walks the\n * `auth-provider` collection registered by `local-auth` and any\n * additional `auth-*` addons (OIDC, SAML, LDAP, …) and returns a\n * curated view.\n *\n * The actual login flow still goes through `auth-provider`\n * collection methods (validateCredentials / getLoginUrl / …); this\n * cap exists so the admin UI's \"Authentication\" page and the login\n * screen's provider picker have ONE place to query instead of\n * walking the collection themselves.\n */\nimport {\n BaseAddon,\n authenticationCapability,\n type AuthProviderInfo,\n type IAuthenticationProvider,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface AuthProviderRegistrationLike {\n readonly addonId?: string\n readonly displayName?: string\n readonly icon?: string\n readonly hasRedirectFlow?: boolean\n readonly hasCredentialFlow?: boolean\n readonly status?: string\n}\n\nexport class AuthOrchestratorAddon extends BaseAddon<Record<string, never>> {\n constructor() {\n super({})\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: IAuthenticationProvider = {\n listProviders: async () => this.listProviders(),\n setProviderEnabled: async () => {\n // Persistence is per-addon today — operators toggle enabled\n // via the provider addon's own settings panel. The orchestrator\n // exposes the affordance for forward-compat (UI may move the\n // enabled toggle into the Authentication page later) but\n // currently no-ops the persistence side.\n return { success: true as const }\n },\n }\n this.ctx.logger.info('Authentication orchestrator initialized')\n return [{ capability: authenticationCapability, provider }]\n }\n\n private async listProviders(): Promise<readonly AuthProviderInfo[]> {\n const entries = this.capabilities?.getCollectionEntries<AuthProviderRegistrationLike>('auth-provider') ?? []\n return entries.map(([addonId, raw]) => ({\n addonId: raw?.addonId ?? addonId,\n displayName: raw?.displayName ?? addonId,\n ...(raw?.icon !== undefined ? { icon: raw.icon } : {}),\n hasRedirectFlow: raw?.hasRedirectFlow ?? false,\n hasCredentialFlow: raw?.hasCredentialFlow ?? (addonId === 'local-auth'),\n ...(raw?.status !== undefined ? { status: raw.status } : {}),\n enabled: true,\n }))\n }\n}\n\nexport default AuthOrchestratorAddon\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA+BA,IAAa,wBAAb,cAA2C,gBAAA,UAAiC;CAC1E,cAAc;EACZ,MAAM,EAAE,CAAC;;CAGX,MAAgB,eAAgD;EAC9D,MAAM,WAAoC;GACxC,eAAe,YAAY,KAAK,eAAe;GAC/C,oBAAoB,YAAY;IAM9B,OAAO,EAAE,SAAS,MAAe;;GAEpC;EACD,KAAK,IAAI,OAAO,KAAK,0CAA0C;EAC/D,OAAO,CAAC;GAAE,YAAY,gBAAA;GAA0B;GAAU,CAAC;;CAG7D,MAAc,gBAAsD;EAElE,QADgB,KAAK,cAAc,qBAAmD,gBAAgB,IAAI,EAAE,EAC7F,KAAK,CAAC,SAAS,UAAU;GACtC,SAAS,KAAK,WAAW;GACzB,aAAa,KAAK,eAAe;GACjC,GAAI,KAAK,SAAS,KAAA,IAAY,EAAE,MAAM,IAAI,MAAM,GAAG,EAAE;GACrD,iBAAiB,KAAK,mBAAmB;GACzC,mBAAmB,KAAK,qBAAsB,YAAY;GAC1D,GAAI,KAAK,WAAW,KAAA,IAAY,EAAE,QAAQ,IAAI,QAAQ,GAAG,EAAE;GAC3D,SAAS;GACV,EAAE"}
@@ -35,7 +35,6 @@ var AuthOrchestratorAddon = class extends BaseAddon {
35
35
  return (this.capabilities?.getCollectionEntries("auth-provider") ?? []).map(([addonId, raw]) => ({
36
36
  addonId: raw?.addonId ?? addonId,
37
37
  displayName: raw?.displayName ?? addonId,
38
- kind: raw?.kind ?? (addonId === "local-auth" ? "local" : "other"),
39
38
  ...raw?.icon !== void 0 ? { icon: raw.icon } : {},
40
39
  hasRedirectFlow: raw?.hasRedirectFlow ?? false,
41
40
  hasCredentialFlow: raw?.hasCredentialFlow ?? addonId === "local-auth",
@@ -1 +1 @@
1
- {"version":3,"file":"auth-orchestrator.addon.mjs","names":[],"sources":["../../../src/builtins/auth-orchestrator/auth-orchestrator.addon.ts"],"sourcesContent":["/**\n * Authentication orchestrator — singleton facade over the\n * `auth-provider` collection. Mirrors the backup-orchestrator pattern:\n * UI consumers go through this builtin, which walks the\n * `auth-provider` collection registered by `local-auth` and any\n * additional `auth-*` addons (OIDC, SAML, LDAP, …) and returns a\n * curated view.\n *\n * The actual login flow still goes through `auth-provider`\n * collection methods (validateCredentials / getLoginUrl / …); this\n * cap exists so the admin UI's \"Authentication\" page and the login\n * screen's provider picker have ONE place to query instead of\n * walking the collection themselves.\n */\nimport {\n BaseAddon,\n authenticationCapability,\n type AuthProviderInfo,\n type IAuthenticationProvider,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface AuthProviderRegistrationLike {\n readonly addonId?: string\n readonly displayName?: string\n readonly kind?: AuthProviderInfo['kind']\n readonly icon?: string\n readonly hasRedirectFlow?: boolean\n readonly hasCredentialFlow?: boolean\n readonly status?: string\n}\n\nexport class AuthOrchestratorAddon extends BaseAddon<Record<string, never>> {\n constructor() {\n super({})\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: IAuthenticationProvider = {\n listProviders: async () => this.listProviders(),\n setProviderEnabled: async () => {\n // Persistence is per-addon today — operators toggle enabled\n // via the provider addon's own settings panel. The orchestrator\n // exposes the affordance for forward-compat (UI may move the\n // enabled toggle into the Authentication page later) but\n // currently no-ops the persistence side.\n return { success: true as const }\n },\n }\n this.ctx.logger.info('Authentication orchestrator initialized')\n return [{ capability: authenticationCapability, provider }]\n }\n\n private async listProviders(): Promise<readonly AuthProviderInfo[]> {\n const entries = this.capabilities?.getCollectionEntries<AuthProviderRegistrationLike>('auth-provider') ?? []\n return entries.map(([addonId, raw]) => ({\n addonId: raw?.addonId ?? addonId,\n displayName: raw?.displayName ?? addonId,\n kind: raw?.kind ?? (addonId === 'local-auth' ? ('local' as const) : ('other' as const)),\n ...(raw?.icon !== undefined ? { icon: raw.icon } : {}),\n hasRedirectFlow: raw?.hasRedirectFlow ?? false,\n hasCredentialFlow: raw?.hasCredentialFlow ?? (addonId === 'local-auth'),\n ...(raw?.status !== undefined ? { status: raw.status } : {}),\n enabled: true,\n }))\n }\n}\n\nexport default AuthOrchestratorAddon\n"],"mappings":";;;;;;;;;;;;;;;;AAgCA,IAAa,wBAAb,cAA2C,UAAiC;CAC1E,cAAc;EACZ,MAAM,EAAE,CAAC;;CAGX,MAAgB,eAAgD;EAC9D,MAAM,WAAoC;GACxC,eAAe,YAAY,KAAK,eAAe;GAC/C,oBAAoB,YAAY;IAM9B,OAAO,EAAE,SAAS,MAAe;;GAEpC;EACD,KAAK,IAAI,OAAO,KAAK,0CAA0C;EAC/D,OAAO,CAAC;GAAE,YAAY;GAA0B;GAAU,CAAC;;CAG7D,MAAc,gBAAsD;EAElE,QADgB,KAAK,cAAc,qBAAmD,gBAAgB,IAAI,EAAE,EAC7F,KAAK,CAAC,SAAS,UAAU;GACtC,SAAS,KAAK,WAAW;GACzB,aAAa,KAAK,eAAe;GACjC,MAAM,KAAK,SAAS,YAAY,eAAgB,UAAqB;GACrE,GAAI,KAAK,SAAS,KAAA,IAAY,EAAE,MAAM,IAAI,MAAM,GAAG,EAAE;GACrD,iBAAiB,KAAK,mBAAmB;GACzC,mBAAmB,KAAK,qBAAsB,YAAY;GAC1D,GAAI,KAAK,WAAW,KAAA,IAAY,EAAE,QAAQ,IAAI,QAAQ,GAAG,EAAE;GAC3D,SAAS;GACV,EAAE"}
1
+ {"version":3,"file":"auth-orchestrator.addon.mjs","names":[],"sources":["../../../src/builtins/auth-orchestrator/auth-orchestrator.addon.ts"],"sourcesContent":["/**\n * Authentication orchestrator — singleton facade over the\n * `auth-provider` collection. Mirrors the backup-orchestrator pattern:\n * UI consumers go through this builtin, which walks the\n * `auth-provider` collection registered by `local-auth` and any\n * additional `auth-*` addons (OIDC, SAML, LDAP, …) and returns a\n * curated view.\n *\n * The actual login flow still goes through `auth-provider`\n * collection methods (validateCredentials / getLoginUrl / …); this\n * cap exists so the admin UI's \"Authentication\" page and the login\n * screen's provider picker have ONE place to query instead of\n * walking the collection themselves.\n */\nimport {\n BaseAddon,\n authenticationCapability,\n type AuthProviderInfo,\n type IAuthenticationProvider,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface AuthProviderRegistrationLike {\n readonly addonId?: string\n readonly displayName?: string\n readonly icon?: string\n readonly hasRedirectFlow?: boolean\n readonly hasCredentialFlow?: boolean\n readonly status?: string\n}\n\nexport class AuthOrchestratorAddon extends BaseAddon<Record<string, never>> {\n constructor() {\n super({})\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: IAuthenticationProvider = {\n listProviders: async () => this.listProviders(),\n setProviderEnabled: async () => {\n // Persistence is per-addon today — operators toggle enabled\n // via the provider addon's own settings panel. The orchestrator\n // exposes the affordance for forward-compat (UI may move the\n // enabled toggle into the Authentication page later) but\n // currently no-ops the persistence side.\n return { success: true as const }\n },\n }\n this.ctx.logger.info('Authentication orchestrator initialized')\n return [{ capability: authenticationCapability, provider }]\n }\n\n private async listProviders(): Promise<readonly AuthProviderInfo[]> {\n const entries = this.capabilities?.getCollectionEntries<AuthProviderRegistrationLike>('auth-provider') ?? []\n return entries.map(([addonId, raw]) => ({\n addonId: raw?.addonId ?? addonId,\n displayName: raw?.displayName ?? addonId,\n ...(raw?.icon !== undefined ? { icon: raw.icon } : {}),\n hasRedirectFlow: raw?.hasRedirectFlow ?? false,\n hasCredentialFlow: raw?.hasCredentialFlow ?? (addonId === 'local-auth'),\n ...(raw?.status !== undefined ? { status: raw.status } : {}),\n enabled: true,\n }))\n }\n}\n\nexport default AuthOrchestratorAddon\n"],"mappings":";;;;;;;;;;;;;;;;AA+BA,IAAa,wBAAb,cAA2C,UAAiC;CAC1E,cAAc;EACZ,MAAM,EAAE,CAAC;;CAGX,MAAgB,eAAgD;EAC9D,MAAM,WAAoC;GACxC,eAAe,YAAY,KAAK,eAAe;GAC/C,oBAAoB,YAAY;IAM9B,OAAO,EAAE,SAAS,MAAe;;GAEpC;EACD,KAAK,IAAI,OAAO,KAAK,0CAA0C;EAC/D,OAAO,CAAC;GAAE,YAAY;GAA0B;GAAU,CAAC;;CAG7D,MAAc,gBAAsD;EAElE,QADgB,KAAK,cAAc,qBAAmD,gBAAgB,IAAI,EAAE,EAC7F,KAAK,CAAC,SAAS,UAAU;GACtC,SAAS,KAAK,WAAW;GACzB,aAAa,KAAK,eAAe;GACjC,GAAI,KAAK,SAAS,KAAA,IAAY,EAAE,MAAM,IAAI,MAAM,GAAG,EAAE;GACrD,iBAAiB,KAAK,mBAAmB;GACzC,mBAAmB,KAAK,qBAAsB,YAAY;GAC1D,GAAI,KAAK,WAAW,KAAA,IAAY,EAAE,QAAQ,IAAI,QAAQ,GAAG,EAAE;GAC3D,SAAS;GACV,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"file":"remote-access-orchestrator.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,SAAS,EAIT,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAA;AAiBxB,UAAU,8BAA8B;IACtC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;CAC7C;AAED,qBAAa,6BAA8B,SAAQ,SAAS,CAAC,8BAA8B,CAAC;;cAK1E,YAAY,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;YAgCjD,yBAAyB;YAiCzB,WAAW;IAQzB,OAAO,CAAC,WAAW;YAQL,aAAa;CAmC5B;AAED,eAAe,6BAA6B,CAAA"}
1
+ {"version":3,"file":"remote-access-orchestrator.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,SAAS,EAIT,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAA;AAgBxB,UAAU,8BAA8B;IACtC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;CAC7C;AAED,qBAAa,6BAA8B,SAAQ,SAAS,CAAC,8BAA8B,CAAC;;cAK1E,YAAY,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;YAgCjD,yBAAyB;YAiCzB,WAAW;IAQzB,OAAO,CAAC,WAAW;YAQL,aAAa;CAkC5B;AAED,eAAe,6BAA6B,CAAA"}
@@ -104,7 +104,6 @@ var RemoteAccessOrchestratorAddon = class extends _camstack_types.BaseAddon {
104
104
  out.push({
105
105
  addonId,
106
106
  displayName: impl.displayName ?? addonId,
107
- kind: impl.kind ?? (addonId.includes("cloudflare") ? "cloudflare-tunnel" : "other"),
108
107
  enabled: enabled.has(addonId),
109
108
  connected,
110
109
  endpoint,
@@ -1 +1 @@
1
- {"version":3,"file":"remote-access-orchestrator.addon.js","names":[],"sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"sourcesContent":["/**\n * Remote-access orchestrator — singleton facade over the\n * `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).\n * Mirrors the auth-orchestrator and backup-orchestrator patterns.\n *\n * Persistence + autostart contract:\n * The orchestrator owns the \"operator wants this provider running\"\n * intent — a `enabledProviders: string[]` slice in its addon-store\n * blob (BaseAddon.config). On boot we iterate the list and call\n * `provider.start()` for each enabled entry. `startProvider` /\n * `stopProvider` mutate this list so a Start press persists across\n * restarts. Same shape as turn-orchestrator's setProviderEnabled.\n */\nimport {\n BaseAddon,\n remoteAccessCapability,\n type IRemoteAccessOrchestrator,\n type RemoteAccessProviderInfo,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface NetworkAccessLike {\n start?: () => Promise<{ url: string; hostname: string; port: number; protocol: 'http' | 'https' }>\n stop?: () => Promise<void>\n getStatus?: () => Promise<{\n connected: boolean\n endpoint: { url: string; hostname: string; port: number; protocol: 'http' | 'https' } | null\n error?: string\n }>\n}\n\ninterface NetworkAccessRegistrationMeta {\n readonly displayName?: string\n readonly kind?: RemoteAccessProviderInfo['kind']\n}\n\ninterface RemoteAccessOrchestratorConfig {\n /**\n * addonIds the operator has explicitly Started. Auto-respawned on\n * boot so a tunnel set up once stays up across hub restarts.\n */\n readonly enabledProviders: readonly string[]\n}\n\nexport class RemoteAccessOrchestratorAddon extends BaseAddon<RemoteAccessOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [] })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: IRemoteAccessOrchestrator = {\n listProviders: async () => this.listProviders(),\n startProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) throw new Error(`Remote-access provider \"${addonId}\" does not support start`)\n const endpoint = await impl.start()\n // Persist intent — next boot will auto-respawn this provider.\n await this.markEnabled(addonId, true)\n return endpoint\n },\n stopProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (impl?.stop) await impl.stop()\n // Clear intent — boot must NOT respawn this on next start.\n await this.markEnabled(addonId, false)\n return { success: true as const }\n },\n } satisfies IRemoteAccessOrchestrator\n this.ctx.logger.info('Remote-access orchestrator initialized', {\n meta: { enabledCount: this.config.enabledProviders.length },\n })\n // Defer autostart to next tick so the orchestrator's own provider\n // registration completes first. `resolveImpl` reads from the\n // capabilities registry which only sees in-process / cluster-mirrored\n // providers once they've ALSO registered — small delay gives the\n // cluster bridge time to discover them on cold boot. Errors are\n // logged but never block init.\n setImmediate(() => { this.autoStartEnabledProviders() })\n return [{ capability: remoteAccessCapability, provider }]\n }\n\n private async autoStartEnabledProviders(): Promise<void> {\n const ids = this.config.enabledProviders\n if (ids.length === 0) return\n this.ctx.logger.info('Auto-starting enabled remote-access providers', {\n meta: { addonIds: [...ids] },\n })\n for (const addonId of ids) {\n try {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) {\n // Provider isn't loaded yet (worker bridge still hydrating)\n // OR it doesn't implement start. Log + skip; the operator can\n // retry from the UI when the addon is ready.\n this.ctx.logger.warn('autostart: provider not ready or unsupported', {\n meta: { addonId, hasImpl: !!impl, hasStart: !!impl?.start },\n })\n continue\n }\n const endpoint = await impl.start()\n this.ctx.logger.info('autostart: provider started', {\n meta: { addonId, url: endpoint.url },\n })\n } catch (err) {\n this.ctx.logger.error('autostart: provider start failed', {\n meta: {\n addonId,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n }\n }\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = new Set(this.config.enabledProviders)\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n if (wasEnabled === enabled) return\n await this.updateGlobalSettings({ enabledProviders: [...current] })\n }\n\n private resolveImpl(addonId: string): (NetworkAccessLike & NetworkAccessRegistrationMeta) | null {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const found = entries.find(([id]) => id === addonId)\n return found?.[1] ?? null\n }\n\n private async listProviders(): Promise<readonly RemoteAccessProviderInfo[]> {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const enabled = new Set(this.config.enabledProviders)\n const out: RemoteAccessProviderInfo[] = []\n for (const [addonId, impl] of entries) {\n let connected = false\n let endpoint: RemoteAccessProviderInfo['endpoint'] = null\n let error: string | undefined\n if (impl.getStatus) {\n try {\n const s = await impl.getStatus()\n connected = s.connected\n endpoint = s.endpoint\n error = s.error\n } catch (err) {\n error = err instanceof Error ? err.message : String(err)\n }\n }\n out.push({\n addonId,\n displayName: impl.displayName ?? addonId,\n kind: impl.kind ?? (addonId.includes('cloudflare') ? 'cloudflare-tunnel' as const : 'other' as const),\n // `enabled` is now the operator's persisted intent — orthogonal\n // to `connected` (which reflects the live tunnel state). Boot\n // tries to bring enabled→connected automatically.\n enabled: enabled.has(addonId),\n connected,\n endpoint,\n ...(error !== undefined ? { error } : {}),\n })\n }\n return out\n }\n}\n\nexport default RemoteAccessOrchestratorAddon\n"],"mappings":"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"}
1
+ {"version":3,"file":"remote-access-orchestrator.addon.js","names":[],"sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"sourcesContent":["/**\n * Remote-access orchestrator — singleton facade over the\n * `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).\n * Mirrors the auth-orchestrator and backup-orchestrator patterns.\n *\n * Persistence + autostart contract:\n * The orchestrator owns the \"operator wants this provider running\"\n * intent — a `enabledProviders: string[]` slice in its addon-store\n * blob (BaseAddon.config). On boot we iterate the list and call\n * `provider.start()` for each enabled entry. `startProvider` /\n * `stopProvider` mutate this list so a Start press persists across\n * restarts. Same shape as turn-orchestrator's setProviderEnabled.\n */\nimport {\n BaseAddon,\n remoteAccessCapability,\n type IRemoteAccessOrchestrator,\n type RemoteAccessProviderInfo,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface NetworkAccessLike {\n start?: () => Promise<{ url: string; hostname: string; port: number; protocol: 'http' | 'https' }>\n stop?: () => Promise<void>\n getStatus?: () => Promise<{\n connected: boolean\n endpoint: { url: string; hostname: string; port: number; protocol: 'http' | 'https' } | null\n error?: string\n }>\n}\n\ninterface NetworkAccessRegistrationMeta {\n readonly displayName?: string\n}\n\ninterface RemoteAccessOrchestratorConfig {\n /**\n * addonIds the operator has explicitly Started. Auto-respawned on\n * boot so a tunnel set up once stays up across hub restarts.\n */\n readonly enabledProviders: readonly string[]\n}\n\nexport class RemoteAccessOrchestratorAddon extends BaseAddon<RemoteAccessOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [] })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: IRemoteAccessOrchestrator = {\n listProviders: async () => this.listProviders(),\n startProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) throw new Error(`Remote-access provider \"${addonId}\" does not support start`)\n const endpoint = await impl.start()\n // Persist intent — next boot will auto-respawn this provider.\n await this.markEnabled(addonId, true)\n return endpoint\n },\n stopProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (impl?.stop) await impl.stop()\n // Clear intent — boot must NOT respawn this on next start.\n await this.markEnabled(addonId, false)\n return { success: true as const }\n },\n } satisfies IRemoteAccessOrchestrator\n this.ctx.logger.info('Remote-access orchestrator initialized', {\n meta: { enabledCount: this.config.enabledProviders.length },\n })\n // Defer autostart to next tick so the orchestrator's own provider\n // registration completes first. `resolveImpl` reads from the\n // capabilities registry which only sees in-process / cluster-mirrored\n // providers once they've ALSO registered — small delay gives the\n // cluster bridge time to discover them on cold boot. Errors are\n // logged but never block init.\n setImmediate(() => { this.autoStartEnabledProviders() })\n return [{ capability: remoteAccessCapability, provider }]\n }\n\n private async autoStartEnabledProviders(): Promise<void> {\n const ids = this.config.enabledProviders\n if (ids.length === 0) return\n this.ctx.logger.info('Auto-starting enabled remote-access providers', {\n meta: { addonIds: [...ids] },\n })\n for (const addonId of ids) {\n try {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) {\n // Provider isn't loaded yet (worker bridge still hydrating)\n // OR it doesn't implement start. Log + skip; the operator can\n // retry from the UI when the addon is ready.\n this.ctx.logger.warn('autostart: provider not ready or unsupported', {\n meta: { addonId, hasImpl: !!impl, hasStart: !!impl?.start },\n })\n continue\n }\n const endpoint = await impl.start()\n this.ctx.logger.info('autostart: provider started', {\n meta: { addonId, url: endpoint.url },\n })\n } catch (err) {\n this.ctx.logger.error('autostart: provider start failed', {\n meta: {\n addonId,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n }\n }\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = new Set(this.config.enabledProviders)\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n if (wasEnabled === enabled) return\n await this.updateGlobalSettings({ enabledProviders: [...current] })\n }\n\n private resolveImpl(addonId: string): (NetworkAccessLike & NetworkAccessRegistrationMeta) | null {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const found = entries.find(([id]) => id === addonId)\n return found?.[1] ?? null\n }\n\n private async listProviders(): Promise<readonly RemoteAccessProviderInfo[]> {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const enabled = new Set(this.config.enabledProviders)\n const out: RemoteAccessProviderInfo[] = []\n for (const [addonId, impl] of entries) {\n let connected = false\n let endpoint: RemoteAccessProviderInfo['endpoint'] = null\n let error: string | undefined\n if (impl.getStatus) {\n try {\n const s = await impl.getStatus()\n connected = s.connected\n endpoint = s.endpoint\n error = s.error\n } catch (err) {\n error = err instanceof Error ? err.message : String(err)\n }\n }\n out.push({\n addonId,\n displayName: impl.displayName ?? addonId,\n // `enabled` is now the operator's persisted intent — orthogonal\n // to `connected` (which reflects the live tunnel state). Boot\n // tries to bring enabled→connected automatically.\n enabled: enabled.has(addonId),\n connected,\n endpoint,\n ...(error !== undefined ? { error } : {}),\n })\n }\n return out\n }\n}\n\nexport default RemoteAccessOrchestratorAddon\n"],"mappings":"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"}
@@ -99,7 +99,6 @@ var RemoteAccessOrchestratorAddon = class extends BaseAddon {
99
99
  out.push({
100
100
  addonId,
101
101
  displayName: impl.displayName ?? addonId,
102
- kind: impl.kind ?? (addonId.includes("cloudflare") ? "cloudflare-tunnel" : "other"),
103
102
  enabled: enabled.has(addonId),
104
103
  connected,
105
104
  endpoint,
@@ -1 +1 @@
1
- {"version":3,"file":"remote-access-orchestrator.addon.mjs","names":[],"sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"sourcesContent":["/**\n * Remote-access orchestrator — singleton facade over the\n * `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).\n * Mirrors the auth-orchestrator and backup-orchestrator patterns.\n *\n * Persistence + autostart contract:\n * The orchestrator owns the \"operator wants this provider running\"\n * intent — a `enabledProviders: string[]` slice in its addon-store\n * blob (BaseAddon.config). On boot we iterate the list and call\n * `provider.start()` for each enabled entry. `startProvider` /\n * `stopProvider` mutate this list so a Start press persists across\n * restarts. Same shape as turn-orchestrator's setProviderEnabled.\n */\nimport {\n BaseAddon,\n remoteAccessCapability,\n type IRemoteAccessOrchestrator,\n type RemoteAccessProviderInfo,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface NetworkAccessLike {\n start?: () => Promise<{ url: string; hostname: string; port: number; protocol: 'http' | 'https' }>\n stop?: () => Promise<void>\n getStatus?: () => Promise<{\n connected: boolean\n endpoint: { url: string; hostname: string; port: number; protocol: 'http' | 'https' } | null\n error?: string\n }>\n}\n\ninterface NetworkAccessRegistrationMeta {\n readonly displayName?: string\n readonly kind?: RemoteAccessProviderInfo['kind']\n}\n\ninterface RemoteAccessOrchestratorConfig {\n /**\n * addonIds the operator has explicitly Started. Auto-respawned on\n * boot so a tunnel set up once stays up across hub restarts.\n */\n readonly enabledProviders: readonly string[]\n}\n\nexport class RemoteAccessOrchestratorAddon extends BaseAddon<RemoteAccessOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [] })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: IRemoteAccessOrchestrator = {\n listProviders: async () => this.listProviders(),\n startProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) throw new Error(`Remote-access provider \"${addonId}\" does not support start`)\n const endpoint = await impl.start()\n // Persist intent — next boot will auto-respawn this provider.\n await this.markEnabled(addonId, true)\n return endpoint\n },\n stopProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (impl?.stop) await impl.stop()\n // Clear intent — boot must NOT respawn this on next start.\n await this.markEnabled(addonId, false)\n return { success: true as const }\n },\n } satisfies IRemoteAccessOrchestrator\n this.ctx.logger.info('Remote-access orchestrator initialized', {\n meta: { enabledCount: this.config.enabledProviders.length },\n })\n // Defer autostart to next tick so the orchestrator's own provider\n // registration completes first. `resolveImpl` reads from the\n // capabilities registry which only sees in-process / cluster-mirrored\n // providers once they've ALSO registered — small delay gives the\n // cluster bridge time to discover them on cold boot. Errors are\n // logged but never block init.\n setImmediate(() => { this.autoStartEnabledProviders() })\n return [{ capability: remoteAccessCapability, provider }]\n }\n\n private async autoStartEnabledProviders(): Promise<void> {\n const ids = this.config.enabledProviders\n if (ids.length === 0) return\n this.ctx.logger.info('Auto-starting enabled remote-access providers', {\n meta: { addonIds: [...ids] },\n })\n for (const addonId of ids) {\n try {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) {\n // Provider isn't loaded yet (worker bridge still hydrating)\n // OR it doesn't implement start. Log + skip; the operator can\n // retry from the UI when the addon is ready.\n this.ctx.logger.warn('autostart: provider not ready or unsupported', {\n meta: { addonId, hasImpl: !!impl, hasStart: !!impl?.start },\n })\n continue\n }\n const endpoint = await impl.start()\n this.ctx.logger.info('autostart: provider started', {\n meta: { addonId, url: endpoint.url },\n })\n } catch (err) {\n this.ctx.logger.error('autostart: provider start failed', {\n meta: {\n addonId,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n }\n }\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = new Set(this.config.enabledProviders)\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n if (wasEnabled === enabled) return\n await this.updateGlobalSettings({ enabledProviders: [...current] })\n }\n\n private resolveImpl(addonId: string): (NetworkAccessLike & NetworkAccessRegistrationMeta) | null {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const found = entries.find(([id]) => id === addonId)\n return found?.[1] ?? null\n }\n\n private async listProviders(): Promise<readonly RemoteAccessProviderInfo[]> {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const enabled = new Set(this.config.enabledProviders)\n const out: RemoteAccessProviderInfo[] = []\n for (const [addonId, impl] of entries) {\n let connected = false\n let endpoint: RemoteAccessProviderInfo['endpoint'] = null\n let error: string | undefined\n if (impl.getStatus) {\n try {\n const s = await impl.getStatus()\n connected = s.connected\n endpoint = s.endpoint\n error = s.error\n } catch (err) {\n error = err instanceof Error ? err.message : String(err)\n }\n }\n out.push({\n addonId,\n displayName: impl.displayName ?? addonId,\n kind: impl.kind ?? (addonId.includes('cloudflare') ? 'cloudflare-tunnel' as const : 'other' as const),\n // `enabled` is now the operator's persisted intent — orthogonal\n // to `connected` (which reflects the live tunnel state). Boot\n // tries to bring enabled→connected automatically.\n enabled: enabled.has(addonId),\n connected,\n endpoint,\n ...(error !== undefined ? { error } : {}),\n })\n }\n return out\n }\n}\n\nexport default RemoteAccessOrchestratorAddon\n"],"mappings":"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"}
1
+ {"version":3,"file":"remote-access-orchestrator.addon.mjs","names":[],"sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"sourcesContent":["/**\n * Remote-access orchestrator — singleton facade over the\n * `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).\n * Mirrors the auth-orchestrator and backup-orchestrator patterns.\n *\n * Persistence + autostart contract:\n * The orchestrator owns the \"operator wants this provider running\"\n * intent — a `enabledProviders: string[]` slice in its addon-store\n * blob (BaseAddon.config). On boot we iterate the list and call\n * `provider.start()` for each enabled entry. `startProvider` /\n * `stopProvider` mutate this list so a Start press persists across\n * restarts. Same shape as turn-orchestrator's setProviderEnabled.\n */\nimport {\n BaseAddon,\n remoteAccessCapability,\n type IRemoteAccessOrchestrator,\n type RemoteAccessProviderInfo,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface NetworkAccessLike {\n start?: () => Promise<{ url: string; hostname: string; port: number; protocol: 'http' | 'https' }>\n stop?: () => Promise<void>\n getStatus?: () => Promise<{\n connected: boolean\n endpoint: { url: string; hostname: string; port: number; protocol: 'http' | 'https' } | null\n error?: string\n }>\n}\n\ninterface NetworkAccessRegistrationMeta {\n readonly displayName?: string\n}\n\ninterface RemoteAccessOrchestratorConfig {\n /**\n * addonIds the operator has explicitly Started. Auto-respawned on\n * boot so a tunnel set up once stays up across hub restarts.\n */\n readonly enabledProviders: readonly string[]\n}\n\nexport class RemoteAccessOrchestratorAddon extends BaseAddon<RemoteAccessOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [] })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: IRemoteAccessOrchestrator = {\n listProviders: async () => this.listProviders(),\n startProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) throw new Error(`Remote-access provider \"${addonId}\" does not support start`)\n const endpoint = await impl.start()\n // Persist intent — next boot will auto-respawn this provider.\n await this.markEnabled(addonId, true)\n return endpoint\n },\n stopProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (impl?.stop) await impl.stop()\n // Clear intent — boot must NOT respawn this on next start.\n await this.markEnabled(addonId, false)\n return { success: true as const }\n },\n } satisfies IRemoteAccessOrchestrator\n this.ctx.logger.info('Remote-access orchestrator initialized', {\n meta: { enabledCount: this.config.enabledProviders.length },\n })\n // Defer autostart to next tick so the orchestrator's own provider\n // registration completes first. `resolveImpl` reads from the\n // capabilities registry which only sees in-process / cluster-mirrored\n // providers once they've ALSO registered — small delay gives the\n // cluster bridge time to discover them on cold boot. Errors are\n // logged but never block init.\n setImmediate(() => { this.autoStartEnabledProviders() })\n return [{ capability: remoteAccessCapability, provider }]\n }\n\n private async autoStartEnabledProviders(): Promise<void> {\n const ids = this.config.enabledProviders\n if (ids.length === 0) return\n this.ctx.logger.info('Auto-starting enabled remote-access providers', {\n meta: { addonIds: [...ids] },\n })\n for (const addonId of ids) {\n try {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) {\n // Provider isn't loaded yet (worker bridge still hydrating)\n // OR it doesn't implement start. Log + skip; the operator can\n // retry from the UI when the addon is ready.\n this.ctx.logger.warn('autostart: provider not ready or unsupported', {\n meta: { addonId, hasImpl: !!impl, hasStart: !!impl?.start },\n })\n continue\n }\n const endpoint = await impl.start()\n this.ctx.logger.info('autostart: provider started', {\n meta: { addonId, url: endpoint.url },\n })\n } catch (err) {\n this.ctx.logger.error('autostart: provider start failed', {\n meta: {\n addonId,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n }\n }\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = new Set(this.config.enabledProviders)\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n if (wasEnabled === enabled) return\n await this.updateGlobalSettings({ enabledProviders: [...current] })\n }\n\n private resolveImpl(addonId: string): (NetworkAccessLike & NetworkAccessRegistrationMeta) | null {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const found = entries.find(([id]) => id === addonId)\n return found?.[1] ?? null\n }\n\n private async listProviders(): Promise<readonly RemoteAccessProviderInfo[]> {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const enabled = new Set(this.config.enabledProviders)\n const out: RemoteAccessProviderInfo[] = []\n for (const [addonId, impl] of entries) {\n let connected = false\n let endpoint: RemoteAccessProviderInfo['endpoint'] = null\n let error: string | undefined\n if (impl.getStatus) {\n try {\n const s = await impl.getStatus()\n connected = s.connected\n endpoint = s.endpoint\n error = s.error\n } catch (err) {\n error = err instanceof Error ? err.message : String(err)\n }\n }\n out.push({\n addonId,\n displayName: impl.displayName ?? addonId,\n // `enabled` is now the operator's persisted intent — orthogonal\n // to `connected` (which reflects the live tunnel state). Boot\n // tries to bring enabled→connected automatically.\n enabled: enabled.has(addonId),\n connected,\n endpoint,\n ...(error !== undefined ? { error } : {}),\n })\n }\n return out\n }\n}\n\nexport default RemoteAccessOrchestratorAddon\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"turn-orchestrator.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/turn-orchestrator/turn-orchestrator.addon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,SAAS,EAIT,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAA;AAQxB,UAAU,sBAAsB;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAA;CACzC;AAED,qBAAa,qBAAsB,SAAQ,SAAS,CAAC,sBAAsB,CAAC;;cAK1D,YAAY,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAkB/D,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;YAOZ,WAAW;YAYX,aAAa;YA0Cb,aAAa;CAiB5B;AAED,eAAe,qBAAqB,CAAA"}
1
+ {"version":3,"file":"turn-orchestrator.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/turn-orchestrator/turn-orchestrator.addon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,SAAS,EAIT,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAA;AAOxB,UAAU,sBAAsB;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAA;CACzC;AAED,qBAAa,qBAAsB,SAAQ,SAAS,CAAC,sBAAsB,CAAC;;cAK1D,YAAY,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAkB/D,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;YAOZ,WAAW;YAYX,aAAa;YAyCb,aAAa;CAiB5B;AAED,eAAe,qBAAqB,CAAA"}
@@ -92,7 +92,6 @@ var TurnOrchestratorAddon = class extends _camstack_types.BaseAddon {
92
92
  out.push({
93
93
  addonId,
94
94
  displayName: impl.displayName ?? addonId,
95
- kind: impl.kind ?? (addonId.includes("cloudflare") ? "cloudflare" : addonId.includes("coturn") ? "coturn" : "other"),
96
95
  enabled: isEnabled,
97
96
  serverCount,
98
97
  urls,
@@ -1 +1 @@
1
- {"version":3,"file":"turn-orchestrator.addon.js","names":[],"sources":["../../../src/builtins/turn-orchestrator/turn-orchestrator.addon.ts"],"sourcesContent":["/**\n * TURN orchestrator — singleton facade over the `turn-provider`\n * collection (Cloudflare TURN, coturn, Twilio, …). Mirrors the\n * remote-access-orchestrator persistence + autostart contract:\n * - `enabledProviders: string[]` in addon-store is the operator's\n * intent. Boot reads it back; the Enable/Disable toggle in the\n * admin UI writes it.\n * - `getAllServers()` (consumed by the WebRTC layer) aggregates ICE\n * servers ONLY from enabled providers.\n * - `listProviders()` surfaces the persisted `enabled` flag so the\n * UI can render the correct toggle state on first paint.\n *\n * Provides:\n * - listProviders() — admin UI inventory\n * - getAllServers() — combined ICE server list across enabled providers\n * - setProviderEnabled() — toggle a provider on/off (persisted)\n */\nimport {\n BaseAddon,\n turnOrchestratorCapability,\n type ITurnOrchestratorProvider,\n type TurnProviderInfo,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface TurnProviderLike {\n getTurnServers?: () => Promise<readonly { urls: string | string[]; username?: string; credential?: string }[]>\n readonly displayName?: string\n readonly kind?: TurnProviderInfo['kind']\n}\n\ninterface TurnOrchestratorConfig {\n /**\n * addonIds the operator has explicitly enabled. Persisted in the\n * addon-store; first-launch defaults to \"every provider enabled\" so\n * a freshly-installed TURN addon participates immediately (the\n * operator can disable from the UI). See `effectivelyEnabled`.\n */\n readonly enabledProviders: readonly string[]\n /**\n * Sentinel: true after `enabledProviders` has been written at least\n * once. Distinguishes \"operator hasn't touched the toggles yet —\n * default-enable all\" from \"operator cleared the list — keep it\n * empty\". Same shape as local-network's bootSeeded.\n */\n readonly enabledProvidersSeeded: boolean\n}\n\nexport class TurnOrchestratorAddon extends BaseAddon<TurnOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [], enabledProvidersSeeded: false })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: ITurnOrchestratorProvider = {\n listProviders: async () => this.listProviders(),\n getAllServers: async () => this.getAllServers(),\n setProviderEnabled: async ({ addonId, enabled }) => {\n await this.markEnabled(addonId, enabled)\n return { success: true as const }\n },\n }\n this.ctx.logger.info('TURN orchestrator initialized', {\n meta: {\n enabledCount: this.config.enabledProviders.length,\n seeded: this.config.enabledProvidersSeeded,\n },\n })\n return [{ capability: turnOrchestratorCapability, provider }]\n }\n\n private getEntries(): readonly (readonly [string, TurnProviderLike])[] {\n return this.capabilities?.getCollectionEntries<TurnProviderLike>('turn-provider') ?? []\n }\n\n /**\n * Operator-effective enabled set. Before the first toggle, default\n * to \"every registered provider is enabled\" so a fresh install of a\n * TURN addon participates without manual intervention. After the\n * operator clicks anything, the persisted list is authoritative.\n */\n private effectivelyEnabled(): Set<string> {\n if (this.config.enabledProvidersSeeded) {\n return new Set(this.config.enabledProviders)\n }\n return new Set(this.getEntries().map(([id]) => id))\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = this.effectivelyEnabled()\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n // Always persist with seeded=true so the operator's choice locks in.\n if (wasEnabled === enabled && this.config.enabledProvidersSeeded) return\n await this.updateGlobalSettings({\n enabledProviders: [...current],\n enabledProvidersSeeded: true,\n })\n }\n\n private async listProviders(): Promise<readonly TurnProviderInfo[]> {\n const enabled = this.effectivelyEnabled()\n const out: TurnProviderInfo[] = []\n for (const [addonId, impl] of this.getEntries()) {\n let serverCount = 0\n let urls: readonly string[] = []\n let error: string | undefined\n const isEnabled = enabled.has(addonId)\n // Only fetch live server count for enabled providers — disabled\n // providers shouldn't be making outbound API calls just to render\n // an admin row.\n if (isEnabled && impl.getTurnServers) {\n try {\n const servers = await impl.getTurnServers()\n serverCount = servers.length\n // Flatten the urls field of every ICE-server entry into one\n // list — `{ urls: ['stun:a', 'stun:b'] }` becomes `['stun:a',\n // 'stun:b']`; `{ urls: 'turn:x' }` becomes `['turn:x']`. The\n // admin UI's compact per-provider chip list iterates this.\n const flat: string[] = []\n for (const s of servers) {\n if (Array.isArray(s.urls)) flat.push(...s.urls)\n else if (typeof s.urls === 'string') flat.push(s.urls)\n }\n urls = flat\n } catch (err) {\n error = err instanceof Error ? err.message : String(err)\n }\n }\n out.push({\n addonId,\n displayName: impl.displayName ?? addonId,\n kind: impl.kind ?? (addonId.includes('cloudflare') ? 'cloudflare' as const : addonId.includes('coturn') ? 'coturn' as const : 'other' as const),\n enabled: isEnabled,\n serverCount,\n urls,\n ...(error !== undefined ? { error } : {}),\n })\n }\n return out\n }\n\n private async getAllServers(): Promise<readonly { urls: string | string[]; username?: string; credential?: string }[]> {\n const enabled = this.effectivelyEnabled()\n const out: { urls: string | string[]; username?: string; credential?: string }[] = []\n for (const [addonId, impl] of this.getEntries()) {\n if (!enabled.has(addonId)) continue\n if (!impl.getTurnServers) continue\n try {\n const servers = await impl.getTurnServers()\n out.push(...servers)\n } catch (err) {\n this.ctx.logger.warn('TURN provider failed to enumerate servers', {\n meta: { addonId, error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n return out\n }\n}\n\nexport default TurnOrchestratorAddon\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgDA,IAAa,wBAAb,cAA2C,gBAAA,UAAkC;CAC3E,cAAc;EACZ,MAAM;GAAE,kBAAkB,EAAE;GAAE,wBAAwB;GAAO,CAAC;;CAGhE,MAAgB,eAAgD;EAC9D,MAAM,WAAsC;GAC1C,eAAe,YAAY,KAAK,eAAe;GAC/C,eAAe,YAAY,KAAK,eAAe;GAC/C,oBAAoB,OAAO,EAAE,SAAS,cAAc;IAClD,MAAM,KAAK,YAAY,SAAS,QAAQ;IACxC,OAAO,EAAE,SAAS,MAAe;;GAEpC;EACD,KAAK,IAAI,OAAO,KAAK,iCAAiC,EACpD,MAAM;GACJ,cAAc,KAAK,OAAO,iBAAiB;GAC3C,QAAQ,KAAK,OAAO;GACrB,EACF,CAAC;EACF,OAAO,CAAC;GAAE,YAAY,gBAAA;GAA4B;GAAU,CAAC;;CAG/D,aAAuE;EACrE,OAAO,KAAK,cAAc,qBAAuC,gBAAgB,IAAI,EAAE;;;;;;;;CASzF,qBAA0C;EACxC,IAAI,KAAK,OAAO,wBACd,OAAO,IAAI,IAAI,KAAK,OAAO,iBAAiB;EAE9C,OAAO,IAAI,IAAI,KAAK,YAAY,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC;;CAGrD,MAAc,YAAY,SAAiB,SAAiC;EAC1E,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,aAAa,QAAQ,IAAI,QAAQ;EACvC,IAAI,SAAS,QAAQ,IAAI,QAAQ;OAAO,QAAQ,OAAO,QAAQ;EAE/D,IAAI,eAAe,WAAW,KAAK,OAAO,wBAAwB;EAClE,MAAM,KAAK,qBAAqB;GAC9B,kBAAkB,CAAC,GAAG,QAAQ;GAC9B,wBAAwB;GACzB,CAAC;;CAGJ,MAAc,gBAAsD;EAClE,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,MAA0B,EAAE;EAClC,KAAK,MAAM,CAAC,SAAS,SAAS,KAAK,YAAY,EAAE;GAC/C,IAAI,cAAc;GAClB,IAAI,OAA0B,EAAE;GAChC,IAAI;GACJ,MAAM,YAAY,QAAQ,IAAI,QAAQ;GAItC,IAAI,aAAa,KAAK,gBACpB,IAAI;IACF,MAAM,UAAU,MAAM,KAAK,gBAAgB;IAC3C,cAAc,QAAQ;IAKtB,MAAM,OAAiB,EAAE;IACzB,KAAK,MAAM,KAAK,SACd,IAAI,MAAM,QAAQ,EAAE,KAAK,EAAE,KAAK,KAAK,GAAG,EAAE,KAAK;SAC1C,IAAI,OAAO,EAAE,SAAS,UAAU,KAAK,KAAK,EAAE,KAAK;IAExD,OAAO;YACA,KAAK;IACZ,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;GAG5D,IAAI,KAAK;IACP;IACA,aAAa,KAAK,eAAe;IACjC,MAAM,KAAK,SAAS,QAAQ,SAAS,aAAa,GAAG,eAAwB,QAAQ,SAAS,SAAS,GAAG,WAAoB;IAC9H,SAAS;IACT;IACA;IACA,GAAI,UAAU,KAAA,IAAY,EAAE,OAAO,GAAG,EAAE;IACzC,CAAC;;EAEJ,OAAO;;CAGT,MAAc,gBAAyG;EACrH,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,MAA6E,EAAE;EACrF,KAAK,MAAM,CAAC,SAAS,SAAS,KAAK,YAAY,EAAE;GAC/C,IAAI,CAAC,QAAQ,IAAI,QAAQ,EAAE;GAC3B,IAAI,CAAC,KAAK,gBAAgB;GAC1B,IAAI;IACF,MAAM,UAAU,MAAM,KAAK,gBAAgB;IAC3C,IAAI,KAAK,GAAG,QAAQ;YACb,KAAK;IACZ,KAAK,IAAI,OAAO,KAAK,6CAA6C,EAChE,MAAM;KAAE;KAAS,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;KAAE,EAC3E,CAAC;;;EAGN,OAAO"}
1
+ {"version":3,"file":"turn-orchestrator.addon.js","names":[],"sources":["../../../src/builtins/turn-orchestrator/turn-orchestrator.addon.ts"],"sourcesContent":["/**\n * TURN orchestrator — singleton facade over the `turn-provider`\n * collection (Cloudflare TURN, coturn, Twilio, …). Mirrors the\n * remote-access-orchestrator persistence + autostart contract:\n * - `enabledProviders: string[]` in addon-store is the operator's\n * intent. Boot reads it back; the Enable/Disable toggle in the\n * admin UI writes it.\n * - `getAllServers()` (consumed by the WebRTC layer) aggregates ICE\n * servers ONLY from enabled providers.\n * - `listProviders()` surfaces the persisted `enabled` flag so the\n * UI can render the correct toggle state on first paint.\n *\n * Provides:\n * - listProviders() — admin UI inventory\n * - getAllServers() — combined ICE server list across enabled providers\n * - setProviderEnabled() — toggle a provider on/off (persisted)\n */\nimport {\n BaseAddon,\n turnOrchestratorCapability,\n type ITurnOrchestratorProvider,\n type TurnProviderInfo,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface TurnProviderLike {\n getTurnServers?: () => Promise<readonly { urls: string | string[]; username?: string; credential?: string }[]>\n readonly displayName?: string\n}\n\ninterface TurnOrchestratorConfig {\n /**\n * addonIds the operator has explicitly enabled. Persisted in the\n * addon-store; first-launch defaults to \"every provider enabled\" so\n * a freshly-installed TURN addon participates immediately (the\n * operator can disable from the UI). See `effectivelyEnabled`.\n */\n readonly enabledProviders: readonly string[]\n /**\n * Sentinel: true after `enabledProviders` has been written at least\n * once. Distinguishes \"operator hasn't touched the toggles yet —\n * default-enable all\" from \"operator cleared the list — keep it\n * empty\". Same shape as local-network's bootSeeded.\n */\n readonly enabledProvidersSeeded: boolean\n}\n\nexport class TurnOrchestratorAddon extends BaseAddon<TurnOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [], enabledProvidersSeeded: false })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: ITurnOrchestratorProvider = {\n listProviders: async () => this.listProviders(),\n getAllServers: async () => this.getAllServers(),\n setProviderEnabled: async ({ addonId, enabled }) => {\n await this.markEnabled(addonId, enabled)\n return { success: true as const }\n },\n }\n this.ctx.logger.info('TURN orchestrator initialized', {\n meta: {\n enabledCount: this.config.enabledProviders.length,\n seeded: this.config.enabledProvidersSeeded,\n },\n })\n return [{ capability: turnOrchestratorCapability, provider }]\n }\n\n private getEntries(): readonly (readonly [string, TurnProviderLike])[] {\n return this.capabilities?.getCollectionEntries<TurnProviderLike>('turn-provider') ?? []\n }\n\n /**\n * Operator-effective enabled set. Before the first toggle, default\n * to \"every registered provider is enabled\" so a fresh install of a\n * TURN addon participates without manual intervention. After the\n * operator clicks anything, the persisted list is authoritative.\n */\n private effectivelyEnabled(): Set<string> {\n if (this.config.enabledProvidersSeeded) {\n return new Set(this.config.enabledProviders)\n }\n return new Set(this.getEntries().map(([id]) => id))\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = this.effectivelyEnabled()\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n // Always persist with seeded=true so the operator's choice locks in.\n if (wasEnabled === enabled && this.config.enabledProvidersSeeded) return\n await this.updateGlobalSettings({\n enabledProviders: [...current],\n enabledProvidersSeeded: true,\n })\n }\n\n private async listProviders(): Promise<readonly TurnProviderInfo[]> {\n const enabled = this.effectivelyEnabled()\n const out: TurnProviderInfo[] = []\n for (const [addonId, impl] of this.getEntries()) {\n let serverCount = 0\n let urls: readonly string[] = []\n let error: string | undefined\n const isEnabled = enabled.has(addonId)\n // Only fetch live server count for enabled providers — disabled\n // providers shouldn't be making outbound API calls just to render\n // an admin row.\n if (isEnabled && impl.getTurnServers) {\n try {\n const servers = await impl.getTurnServers()\n serverCount = servers.length\n // Flatten the urls field of every ICE-server entry into one\n // list — `{ urls: ['stun:a', 'stun:b'] }` becomes `['stun:a',\n // 'stun:b']`; `{ urls: 'turn:x' }` becomes `['turn:x']`. The\n // admin UI's compact per-provider chip list iterates this.\n const flat: string[] = []\n for (const s of servers) {\n if (Array.isArray(s.urls)) flat.push(...s.urls)\n else if (typeof s.urls === 'string') flat.push(s.urls)\n }\n urls = flat\n } catch (err) {\n error = err instanceof Error ? err.message : String(err)\n }\n }\n out.push({\n addonId,\n displayName: impl.displayName ?? addonId,\n enabled: isEnabled,\n serverCount,\n urls,\n ...(error !== undefined ? { error } : {}),\n })\n }\n return out\n }\n\n private async getAllServers(): Promise<readonly { urls: string | string[]; username?: string; credential?: string }[]> {\n const enabled = this.effectivelyEnabled()\n const out: { urls: string | string[]; username?: string; credential?: string }[] = []\n for (const [addonId, impl] of this.getEntries()) {\n if (!enabled.has(addonId)) continue\n if (!impl.getTurnServers) continue\n try {\n const servers = await impl.getTurnServers()\n out.push(...servers)\n } catch (err) {\n this.ctx.logger.warn('TURN provider failed to enumerate servers', {\n meta: { addonId, error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n return out\n }\n}\n\nexport default TurnOrchestratorAddon\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA+CA,IAAa,wBAAb,cAA2C,gBAAA,UAAkC;CAC3E,cAAc;EACZ,MAAM;GAAE,kBAAkB,EAAE;GAAE,wBAAwB;GAAO,CAAC;;CAGhE,MAAgB,eAAgD;EAC9D,MAAM,WAAsC;GAC1C,eAAe,YAAY,KAAK,eAAe;GAC/C,eAAe,YAAY,KAAK,eAAe;GAC/C,oBAAoB,OAAO,EAAE,SAAS,cAAc;IAClD,MAAM,KAAK,YAAY,SAAS,QAAQ;IACxC,OAAO,EAAE,SAAS,MAAe;;GAEpC;EACD,KAAK,IAAI,OAAO,KAAK,iCAAiC,EACpD,MAAM;GACJ,cAAc,KAAK,OAAO,iBAAiB;GAC3C,QAAQ,KAAK,OAAO;GACrB,EACF,CAAC;EACF,OAAO,CAAC;GAAE,YAAY,gBAAA;GAA4B;GAAU,CAAC;;CAG/D,aAAuE;EACrE,OAAO,KAAK,cAAc,qBAAuC,gBAAgB,IAAI,EAAE;;;;;;;;CASzF,qBAA0C;EACxC,IAAI,KAAK,OAAO,wBACd,OAAO,IAAI,IAAI,KAAK,OAAO,iBAAiB;EAE9C,OAAO,IAAI,IAAI,KAAK,YAAY,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC;;CAGrD,MAAc,YAAY,SAAiB,SAAiC;EAC1E,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,aAAa,QAAQ,IAAI,QAAQ;EACvC,IAAI,SAAS,QAAQ,IAAI,QAAQ;OAAO,QAAQ,OAAO,QAAQ;EAE/D,IAAI,eAAe,WAAW,KAAK,OAAO,wBAAwB;EAClE,MAAM,KAAK,qBAAqB;GAC9B,kBAAkB,CAAC,GAAG,QAAQ;GAC9B,wBAAwB;GACzB,CAAC;;CAGJ,MAAc,gBAAsD;EAClE,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,MAA0B,EAAE;EAClC,KAAK,MAAM,CAAC,SAAS,SAAS,KAAK,YAAY,EAAE;GAC/C,IAAI,cAAc;GAClB,IAAI,OAA0B,EAAE;GAChC,IAAI;GACJ,MAAM,YAAY,QAAQ,IAAI,QAAQ;GAItC,IAAI,aAAa,KAAK,gBACpB,IAAI;IACF,MAAM,UAAU,MAAM,KAAK,gBAAgB;IAC3C,cAAc,QAAQ;IAKtB,MAAM,OAAiB,EAAE;IACzB,KAAK,MAAM,KAAK,SACd,IAAI,MAAM,QAAQ,EAAE,KAAK,EAAE,KAAK,KAAK,GAAG,EAAE,KAAK;SAC1C,IAAI,OAAO,EAAE,SAAS,UAAU,KAAK,KAAK,EAAE,KAAK;IAExD,OAAO;YACA,KAAK;IACZ,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;GAG5D,IAAI,KAAK;IACP;IACA,aAAa,KAAK,eAAe;IACjC,SAAS;IACT;IACA;IACA,GAAI,UAAU,KAAA,IAAY,EAAE,OAAO,GAAG,EAAE;IACzC,CAAC;;EAEJ,OAAO;;CAGT,MAAc,gBAAyG;EACrH,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,MAA6E,EAAE;EACrF,KAAK,MAAM,CAAC,SAAS,SAAS,KAAK,YAAY,EAAE;GAC/C,IAAI,CAAC,QAAQ,IAAI,QAAQ,EAAE;GAC3B,IAAI,CAAC,KAAK,gBAAgB;GAC1B,IAAI;IACF,MAAM,UAAU,MAAM,KAAK,gBAAgB;IAC3C,IAAI,KAAK,GAAG,QAAQ;YACb,KAAK;IACZ,KAAK,IAAI,OAAO,KAAK,6CAA6C,EAChE,MAAM;KAAE;KAAS,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;KAAE,EAC3E,CAAC;;;EAGN,OAAO"}
@@ -87,7 +87,6 @@ var TurnOrchestratorAddon = class extends BaseAddon {
87
87
  out.push({
88
88
  addonId,
89
89
  displayName: impl.displayName ?? addonId,
90
- kind: impl.kind ?? (addonId.includes("cloudflare") ? "cloudflare" : addonId.includes("coturn") ? "coturn" : "other"),
91
90
  enabled: isEnabled,
92
91
  serverCount,
93
92
  urls,
@@ -1 +1 @@
1
- {"version":3,"file":"turn-orchestrator.addon.mjs","names":[],"sources":["../../../src/builtins/turn-orchestrator/turn-orchestrator.addon.ts"],"sourcesContent":["/**\n * TURN orchestrator — singleton facade over the `turn-provider`\n * collection (Cloudflare TURN, coturn, Twilio, …). Mirrors the\n * remote-access-orchestrator persistence + autostart contract:\n * - `enabledProviders: string[]` in addon-store is the operator's\n * intent. Boot reads it back; the Enable/Disable toggle in the\n * admin UI writes it.\n * - `getAllServers()` (consumed by the WebRTC layer) aggregates ICE\n * servers ONLY from enabled providers.\n * - `listProviders()` surfaces the persisted `enabled` flag so the\n * UI can render the correct toggle state on first paint.\n *\n * Provides:\n * - listProviders() — admin UI inventory\n * - getAllServers() — combined ICE server list across enabled providers\n * - setProviderEnabled() — toggle a provider on/off (persisted)\n */\nimport {\n BaseAddon,\n turnOrchestratorCapability,\n type ITurnOrchestratorProvider,\n type TurnProviderInfo,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface TurnProviderLike {\n getTurnServers?: () => Promise<readonly { urls: string | string[]; username?: string; credential?: string }[]>\n readonly displayName?: string\n readonly kind?: TurnProviderInfo['kind']\n}\n\ninterface TurnOrchestratorConfig {\n /**\n * addonIds the operator has explicitly enabled. Persisted in the\n * addon-store; first-launch defaults to \"every provider enabled\" so\n * a freshly-installed TURN addon participates immediately (the\n * operator can disable from the UI). See `effectivelyEnabled`.\n */\n readonly enabledProviders: readonly string[]\n /**\n * Sentinel: true after `enabledProviders` has been written at least\n * once. Distinguishes \"operator hasn't touched the toggles yet —\n * default-enable all\" from \"operator cleared the list — keep it\n * empty\". Same shape as local-network's bootSeeded.\n */\n readonly enabledProvidersSeeded: boolean\n}\n\nexport class TurnOrchestratorAddon extends BaseAddon<TurnOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [], enabledProvidersSeeded: false })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: ITurnOrchestratorProvider = {\n listProviders: async () => this.listProviders(),\n getAllServers: async () => this.getAllServers(),\n setProviderEnabled: async ({ addonId, enabled }) => {\n await this.markEnabled(addonId, enabled)\n return { success: true as const }\n },\n }\n this.ctx.logger.info('TURN orchestrator initialized', {\n meta: {\n enabledCount: this.config.enabledProviders.length,\n seeded: this.config.enabledProvidersSeeded,\n },\n })\n return [{ capability: turnOrchestratorCapability, provider }]\n }\n\n private getEntries(): readonly (readonly [string, TurnProviderLike])[] {\n return this.capabilities?.getCollectionEntries<TurnProviderLike>('turn-provider') ?? []\n }\n\n /**\n * Operator-effective enabled set. Before the first toggle, default\n * to \"every registered provider is enabled\" so a fresh install of a\n * TURN addon participates without manual intervention. After the\n * operator clicks anything, the persisted list is authoritative.\n */\n private effectivelyEnabled(): Set<string> {\n if (this.config.enabledProvidersSeeded) {\n return new Set(this.config.enabledProviders)\n }\n return new Set(this.getEntries().map(([id]) => id))\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = this.effectivelyEnabled()\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n // Always persist with seeded=true so the operator's choice locks in.\n if (wasEnabled === enabled && this.config.enabledProvidersSeeded) return\n await this.updateGlobalSettings({\n enabledProviders: [...current],\n enabledProvidersSeeded: true,\n })\n }\n\n private async listProviders(): Promise<readonly TurnProviderInfo[]> {\n const enabled = this.effectivelyEnabled()\n const out: TurnProviderInfo[] = []\n for (const [addonId, impl] of this.getEntries()) {\n let serverCount = 0\n let urls: readonly string[] = []\n let error: string | undefined\n const isEnabled = enabled.has(addonId)\n // Only fetch live server count for enabled providers — disabled\n // providers shouldn't be making outbound API calls just to render\n // an admin row.\n if (isEnabled && impl.getTurnServers) {\n try {\n const servers = await impl.getTurnServers()\n serverCount = servers.length\n // Flatten the urls field of every ICE-server entry into one\n // list — `{ urls: ['stun:a', 'stun:b'] }` becomes `['stun:a',\n // 'stun:b']`; `{ urls: 'turn:x' }` becomes `['turn:x']`. The\n // admin UI's compact per-provider chip list iterates this.\n const flat: string[] = []\n for (const s of servers) {\n if (Array.isArray(s.urls)) flat.push(...s.urls)\n else if (typeof s.urls === 'string') flat.push(s.urls)\n }\n urls = flat\n } catch (err) {\n error = err instanceof Error ? err.message : String(err)\n }\n }\n out.push({\n addonId,\n displayName: impl.displayName ?? addonId,\n kind: impl.kind ?? (addonId.includes('cloudflare') ? 'cloudflare' as const : addonId.includes('coturn') ? 'coturn' as const : 'other' as const),\n enabled: isEnabled,\n serverCount,\n urls,\n ...(error !== undefined ? { error } : {}),\n })\n }\n return out\n }\n\n private async getAllServers(): Promise<readonly { urls: string | string[]; username?: string; credential?: string }[]> {\n const enabled = this.effectivelyEnabled()\n const out: { urls: string | string[]; username?: string; credential?: string }[] = []\n for (const [addonId, impl] of this.getEntries()) {\n if (!enabled.has(addonId)) continue\n if (!impl.getTurnServers) continue\n try {\n const servers = await impl.getTurnServers()\n out.push(...servers)\n } catch (err) {\n this.ctx.logger.warn('TURN provider failed to enumerate servers', {\n meta: { addonId, error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n return out\n }\n}\n\nexport default TurnOrchestratorAddon\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgDA,IAAa,wBAAb,cAA2C,UAAkC;CAC3E,cAAc;EACZ,MAAM;GAAE,kBAAkB,EAAE;GAAE,wBAAwB;GAAO,CAAC;;CAGhE,MAAgB,eAAgD;EAC9D,MAAM,WAAsC;GAC1C,eAAe,YAAY,KAAK,eAAe;GAC/C,eAAe,YAAY,KAAK,eAAe;GAC/C,oBAAoB,OAAO,EAAE,SAAS,cAAc;IAClD,MAAM,KAAK,YAAY,SAAS,QAAQ;IACxC,OAAO,EAAE,SAAS,MAAe;;GAEpC;EACD,KAAK,IAAI,OAAO,KAAK,iCAAiC,EACpD,MAAM;GACJ,cAAc,KAAK,OAAO,iBAAiB;GAC3C,QAAQ,KAAK,OAAO;GACrB,EACF,CAAC;EACF,OAAO,CAAC;GAAE,YAAY;GAA4B;GAAU,CAAC;;CAG/D,aAAuE;EACrE,OAAO,KAAK,cAAc,qBAAuC,gBAAgB,IAAI,EAAE;;;;;;;;CASzF,qBAA0C;EACxC,IAAI,KAAK,OAAO,wBACd,OAAO,IAAI,IAAI,KAAK,OAAO,iBAAiB;EAE9C,OAAO,IAAI,IAAI,KAAK,YAAY,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC;;CAGrD,MAAc,YAAY,SAAiB,SAAiC;EAC1E,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,aAAa,QAAQ,IAAI,QAAQ;EACvC,IAAI,SAAS,QAAQ,IAAI,QAAQ;OAAO,QAAQ,OAAO,QAAQ;EAE/D,IAAI,eAAe,WAAW,KAAK,OAAO,wBAAwB;EAClE,MAAM,KAAK,qBAAqB;GAC9B,kBAAkB,CAAC,GAAG,QAAQ;GAC9B,wBAAwB;GACzB,CAAC;;CAGJ,MAAc,gBAAsD;EAClE,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,MAA0B,EAAE;EAClC,KAAK,MAAM,CAAC,SAAS,SAAS,KAAK,YAAY,EAAE;GAC/C,IAAI,cAAc;GAClB,IAAI,OAA0B,EAAE;GAChC,IAAI;GACJ,MAAM,YAAY,QAAQ,IAAI,QAAQ;GAItC,IAAI,aAAa,KAAK,gBACpB,IAAI;IACF,MAAM,UAAU,MAAM,KAAK,gBAAgB;IAC3C,cAAc,QAAQ;IAKtB,MAAM,OAAiB,EAAE;IACzB,KAAK,MAAM,KAAK,SACd,IAAI,MAAM,QAAQ,EAAE,KAAK,EAAE,KAAK,KAAK,GAAG,EAAE,KAAK;SAC1C,IAAI,OAAO,EAAE,SAAS,UAAU,KAAK,KAAK,EAAE,KAAK;IAExD,OAAO;YACA,KAAK;IACZ,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;GAG5D,IAAI,KAAK;IACP;IACA,aAAa,KAAK,eAAe;IACjC,MAAM,KAAK,SAAS,QAAQ,SAAS,aAAa,GAAG,eAAwB,QAAQ,SAAS,SAAS,GAAG,WAAoB;IAC9H,SAAS;IACT;IACA;IACA,GAAI,UAAU,KAAA,IAAY,EAAE,OAAO,GAAG,EAAE;IACzC,CAAC;;EAEJ,OAAO;;CAGT,MAAc,gBAAyG;EACrH,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,MAA6E,EAAE;EACrF,KAAK,MAAM,CAAC,SAAS,SAAS,KAAK,YAAY,EAAE;GAC/C,IAAI,CAAC,QAAQ,IAAI,QAAQ,EAAE;GAC3B,IAAI,CAAC,KAAK,gBAAgB;GAC1B,IAAI;IACF,MAAM,UAAU,MAAM,KAAK,gBAAgB;IAC3C,IAAI,KAAK,GAAG,QAAQ;YACb,KAAK;IACZ,KAAK,IAAI,OAAO,KAAK,6CAA6C,EAChE,MAAM;KAAE;KAAS,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;KAAE,EAC3E,CAAC;;;EAGN,OAAO"}
1
+ {"version":3,"file":"turn-orchestrator.addon.mjs","names":[],"sources":["../../../src/builtins/turn-orchestrator/turn-orchestrator.addon.ts"],"sourcesContent":["/**\n * TURN orchestrator — singleton facade over the `turn-provider`\n * collection (Cloudflare TURN, coturn, Twilio, …). Mirrors the\n * remote-access-orchestrator persistence + autostart contract:\n * - `enabledProviders: string[]` in addon-store is the operator's\n * intent. Boot reads it back; the Enable/Disable toggle in the\n * admin UI writes it.\n * - `getAllServers()` (consumed by the WebRTC layer) aggregates ICE\n * servers ONLY from enabled providers.\n * - `listProviders()` surfaces the persisted `enabled` flag so the\n * UI can render the correct toggle state on first paint.\n *\n * Provides:\n * - listProviders() — admin UI inventory\n * - getAllServers() — combined ICE server list across enabled providers\n * - setProviderEnabled() — toggle a provider on/off (persisted)\n */\nimport {\n BaseAddon,\n turnOrchestratorCapability,\n type ITurnOrchestratorProvider,\n type TurnProviderInfo,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface TurnProviderLike {\n getTurnServers?: () => Promise<readonly { urls: string | string[]; username?: string; credential?: string }[]>\n readonly displayName?: string\n}\n\ninterface TurnOrchestratorConfig {\n /**\n * addonIds the operator has explicitly enabled. Persisted in the\n * addon-store; first-launch defaults to \"every provider enabled\" so\n * a freshly-installed TURN addon participates immediately (the\n * operator can disable from the UI). See `effectivelyEnabled`.\n */\n readonly enabledProviders: readonly string[]\n /**\n * Sentinel: true after `enabledProviders` has been written at least\n * once. Distinguishes \"operator hasn't touched the toggles yet —\n * default-enable all\" from \"operator cleared the list — keep it\n * empty\". Same shape as local-network's bootSeeded.\n */\n readonly enabledProvidersSeeded: boolean\n}\n\nexport class TurnOrchestratorAddon extends BaseAddon<TurnOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [], enabledProvidersSeeded: false })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: ITurnOrchestratorProvider = {\n listProviders: async () => this.listProviders(),\n getAllServers: async () => this.getAllServers(),\n setProviderEnabled: async ({ addonId, enabled }) => {\n await this.markEnabled(addonId, enabled)\n return { success: true as const }\n },\n }\n this.ctx.logger.info('TURN orchestrator initialized', {\n meta: {\n enabledCount: this.config.enabledProviders.length,\n seeded: this.config.enabledProvidersSeeded,\n },\n })\n return [{ capability: turnOrchestratorCapability, provider }]\n }\n\n private getEntries(): readonly (readonly [string, TurnProviderLike])[] {\n return this.capabilities?.getCollectionEntries<TurnProviderLike>('turn-provider') ?? []\n }\n\n /**\n * Operator-effective enabled set. Before the first toggle, default\n * to \"every registered provider is enabled\" so a fresh install of a\n * TURN addon participates without manual intervention. After the\n * operator clicks anything, the persisted list is authoritative.\n */\n private effectivelyEnabled(): Set<string> {\n if (this.config.enabledProvidersSeeded) {\n return new Set(this.config.enabledProviders)\n }\n return new Set(this.getEntries().map(([id]) => id))\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = this.effectivelyEnabled()\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n // Always persist with seeded=true so the operator's choice locks in.\n if (wasEnabled === enabled && this.config.enabledProvidersSeeded) return\n await this.updateGlobalSettings({\n enabledProviders: [...current],\n enabledProvidersSeeded: true,\n })\n }\n\n private async listProviders(): Promise<readonly TurnProviderInfo[]> {\n const enabled = this.effectivelyEnabled()\n const out: TurnProviderInfo[] = []\n for (const [addonId, impl] of this.getEntries()) {\n let serverCount = 0\n let urls: readonly string[] = []\n let error: string | undefined\n const isEnabled = enabled.has(addonId)\n // Only fetch live server count for enabled providers — disabled\n // providers shouldn't be making outbound API calls just to render\n // an admin row.\n if (isEnabled && impl.getTurnServers) {\n try {\n const servers = await impl.getTurnServers()\n serverCount = servers.length\n // Flatten the urls field of every ICE-server entry into one\n // list — `{ urls: ['stun:a', 'stun:b'] }` becomes `['stun:a',\n // 'stun:b']`; `{ urls: 'turn:x' }` becomes `['turn:x']`. The\n // admin UI's compact per-provider chip list iterates this.\n const flat: string[] = []\n for (const s of servers) {\n if (Array.isArray(s.urls)) flat.push(...s.urls)\n else if (typeof s.urls === 'string') flat.push(s.urls)\n }\n urls = flat\n } catch (err) {\n error = err instanceof Error ? err.message : String(err)\n }\n }\n out.push({\n addonId,\n displayName: impl.displayName ?? addonId,\n enabled: isEnabled,\n serverCount,\n urls,\n ...(error !== undefined ? { error } : {}),\n })\n }\n return out\n }\n\n private async getAllServers(): Promise<readonly { urls: string | string[]; username?: string; credential?: string }[]> {\n const enabled = this.effectivelyEnabled()\n const out: { urls: string | string[]; username?: string; credential?: string }[] = []\n for (const [addonId, impl] of this.getEntries()) {\n if (!enabled.has(addonId)) continue\n if (!impl.getTurnServers) continue\n try {\n const servers = await impl.getTurnServers()\n out.push(...servers)\n } catch (err) {\n this.ctx.logger.warn('TURN provider failed to enumerate servers', {\n meta: { addonId, error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n return out\n }\n}\n\nexport default TurnOrchestratorAddon\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+CA,IAAa,wBAAb,cAA2C,UAAkC;CAC3E,cAAc;EACZ,MAAM;GAAE,kBAAkB,EAAE;GAAE,wBAAwB;GAAO,CAAC;;CAGhE,MAAgB,eAAgD;EAC9D,MAAM,WAAsC;GAC1C,eAAe,YAAY,KAAK,eAAe;GAC/C,eAAe,YAAY,KAAK,eAAe;GAC/C,oBAAoB,OAAO,EAAE,SAAS,cAAc;IAClD,MAAM,KAAK,YAAY,SAAS,QAAQ;IACxC,OAAO,EAAE,SAAS,MAAe;;GAEpC;EACD,KAAK,IAAI,OAAO,KAAK,iCAAiC,EACpD,MAAM;GACJ,cAAc,KAAK,OAAO,iBAAiB;GAC3C,QAAQ,KAAK,OAAO;GACrB,EACF,CAAC;EACF,OAAO,CAAC;GAAE,YAAY;GAA4B;GAAU,CAAC;;CAG/D,aAAuE;EACrE,OAAO,KAAK,cAAc,qBAAuC,gBAAgB,IAAI,EAAE;;;;;;;;CASzF,qBAA0C;EACxC,IAAI,KAAK,OAAO,wBACd,OAAO,IAAI,IAAI,KAAK,OAAO,iBAAiB;EAE9C,OAAO,IAAI,IAAI,KAAK,YAAY,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC;;CAGrD,MAAc,YAAY,SAAiB,SAAiC;EAC1E,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,aAAa,QAAQ,IAAI,QAAQ;EACvC,IAAI,SAAS,QAAQ,IAAI,QAAQ;OAAO,QAAQ,OAAO,QAAQ;EAE/D,IAAI,eAAe,WAAW,KAAK,OAAO,wBAAwB;EAClE,MAAM,KAAK,qBAAqB;GAC9B,kBAAkB,CAAC,GAAG,QAAQ;GAC9B,wBAAwB;GACzB,CAAC;;CAGJ,MAAc,gBAAsD;EAClE,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,MAA0B,EAAE;EAClC,KAAK,MAAM,CAAC,SAAS,SAAS,KAAK,YAAY,EAAE;GAC/C,IAAI,cAAc;GAClB,IAAI,OAA0B,EAAE;GAChC,IAAI;GACJ,MAAM,YAAY,QAAQ,IAAI,QAAQ;GAItC,IAAI,aAAa,KAAK,gBACpB,IAAI;IACF,MAAM,UAAU,MAAM,KAAK,gBAAgB;IAC3C,cAAc,QAAQ;IAKtB,MAAM,OAAiB,EAAE;IACzB,KAAK,MAAM,KAAK,SACd,IAAI,MAAM,QAAQ,EAAE,KAAK,EAAE,KAAK,KAAK,GAAG,EAAE,KAAK;SAC1C,IAAI,OAAO,EAAE,SAAS,UAAU,KAAK,KAAK,EAAE,KAAK;IAExD,OAAO;YACA,KAAK;IACZ,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;GAG5D,IAAI,KAAK;IACP;IACA,aAAa,KAAK,eAAe;IACjC,SAAS;IACT;IACA;IACA,GAAI,UAAU,KAAA,IAAY,EAAE,OAAO,GAAG,EAAE;IACzC,CAAC;;EAEJ,OAAO;;CAGT,MAAc,gBAAyG;EACrH,MAAM,UAAU,KAAK,oBAAoB;EACzC,MAAM,MAA6E,EAAE;EACrF,KAAK,MAAM,CAAC,SAAS,SAAS,KAAK,YAAY,EAAE;GAC/C,IAAI,CAAC,QAAQ,IAAI,QAAQ,EAAE;GAC3B,IAAI,CAAC,KAAK,gBAAgB;GAC1B,IAAI;IACF,MAAM,UAAU,MAAM,KAAK,gBAAgB;IAC3C,IAAI,KAAK,GAAG,QAAQ;YACb,KAAK;IACZ,KAAK,IAAI,OAAO,KAAK,6CAA6C,EAChE,MAAM;KAAE;KAAS,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;KAAE,EAC3E,CAAC;;;EAGN,OAAO"}
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ let node_vm = require("node:vm");
37
37
  node_vm = require_chunk.__toESM(node_vm);
38
38
  let node_os = require("node:os");
39
39
  node_os = require_chunk.__toESM(node_os);
40
- //#region ../types/dist/auth-records-Bsjs9Xri.mjs
40
+ //#region ../types/dist/auth-records-COv3plzv.mjs
41
41
  var MODEL_FORMATS = [
42
42
  "onnx",
43
43
  "coreml",
@@ -3308,21 +3308,6 @@ var AuthProviderInfoSchema = zod.z.object({
3308
3308
  addonId: zod.z.string(),
3309
3309
  /** Display label shown on the login button + admin row. */
3310
3310
  displayName: zod.z.string(),
3311
- /**
3312
- * Provider kind — drives icon + grouping in admin UI.
3313
- * - `local` — username/password form (always at top of login page)
3314
- * - `oidc` — OpenID Connect (Google, Microsoft, Okta, generic)
3315
- * - `saml` — SAML 2.0
3316
- * - `ldap` — LDAP / Active Directory
3317
- * - `other` — anything else (custom integrations)
3318
- */
3319
- kind: zod.z.enum([
3320
- "local",
3321
- "oidc",
3322
- "saml",
3323
- "ldap",
3324
- "other"
3325
- ]),
3326
3311
  /** Optional iconography hint (lucide-react icon name OR emoji). */
3327
3312
  icon: zod.z.string().optional(),
3328
3313
  /** When true, the provider exposes a redirect-based login flow
@@ -3365,21 +3350,8 @@ var RemoteAccessEndpointSchema = zod.z.object({
3365
3350
  var RemoteAccessProviderInfoSchema = zod.z.object({
3366
3351
  /** Stable id matching the addon id. */
3367
3352
  addonId: zod.z.string(),
3368
- /** Display label shown on the admin row. */
3353
+ /** Display label shown on the admin row — sourced from the addon manifest. */
3369
3354
  displayName: zod.z.string(),
3370
- /**
3371
- * Provider kind — drives icon + status formatting.
3372
- * - `cloudflare-tunnel`
3373
- * - `ngrok`
3374
- * - `tailscale`
3375
- * - `other`
3376
- */
3377
- kind: zod.z.enum([
3378
- "cloudflare-tunnel",
3379
- "ngrok",
3380
- "tailscale",
3381
- "other"
3382
- ]),
3383
3355
  /** When false, the provider is registered but disabled. */
3384
3356
  enabled: zod.z.boolean(),
3385
3357
  /** True when the underlying tunnel/connection is up. */
@@ -3406,21 +3378,8 @@ method(zod.z.void(), zod.z.array(TurnServerSchema).readonly());
3406
3378
  var TurnProviderInfoSchema = zod.z.object({
3407
3379
  /** Stable id matching the addon id. */
3408
3380
  addonId: zod.z.string(),
3409
- /** Display label shown on the admin row. */
3381
+ /** Display label shown on the admin row — sourced from the addon manifest. */
3410
3382
  displayName: zod.z.string(),
3411
- /**
3412
- * Provider kind — drives icon + grouping.
3413
- * - `cloudflare` — Cloudflare TURN
3414
- * - `coturn` — self-hosted coturn
3415
- * - `twilio` — Twilio Network Traversal
3416
- * - `other` — anything else
3417
- */
3418
- kind: zod.z.enum([
3419
- "cloudflare",
3420
- "coturn",
3421
- "twilio",
3422
- "other"
3423
- ]),
3424
3383
  /** When false, the provider is registered but disabled. */
3425
3384
  enabled: zod.z.boolean(),
3426
3385
  /** Number of servers this provider is currently exposing. */