@kennofizet/apphub-frontend 0.1.8 → 0.1.9

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kennofizet/apphub-frontend",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "App Hub Vue 3 UI — Windows-style desktop shell and modular apps (App Store default).",
5
5
  "main": "./src/index.js",
6
6
  "module": "./src/index.js",
package/src/index.js CHANGED
@@ -1,509 +1,449 @@
1
- import './modules/desktop/styles/desktop.css'
2
- import './modules/desktop/styles/theme.css'
3
- import { reactive } from 'vue'
4
- import { createAppHubApi } from './api/index.js'
5
- import { createCoreApi } from './api/coreApi.js'
6
- import { createZoneContextState } from './composables/createZoneContext.js'
7
- import { APPHUB_ZONE_CONTEXT_KEY } from './composables/useAppHubZoneContext.js'
8
- import { createAppStoreState, provideAppStore } from './modules/app-store/index.js'
9
- import { AppHubDesktop } from './modules/desktop/index.js'
10
- import { AppHubRunner } from './modules/runner/index.js'
11
- import { getAppHubStore, registerAppHubStore, APPHUB_MODULE_STORE_KEY } from './moduleStore.js'
12
- import {
13
- createWindowManagerState,
14
- provideWindowManager,
15
- } from './modules/window-manager/index.js'
16
- import {
17
- evaluateOriginSafety,
18
- parseDevUserFromBootstrap,
19
- parseOriginsFromBootstrap,
20
- } from './utils/originSafety.js'
21
- import { loadDevFriendlyOriginsPreference } from './utils/devOriginSettings.js'
22
- import {
23
- bootstrapResponseFromCache,
24
- loadBootstrapCache,
25
- saveBootstrapCache,
26
- } from './utils/bootstrapCache.js'
27
- import {
28
- apphubDebug,
29
- describeHostApi,
30
- describeStore,
31
- } from './utils/apphubDebug.js'
32
-
33
- const bootstrapInflight = new WeakMap()
34
-
35
- function buildPublicOptions(options = {}) {
36
- const origins = Array.isArray(options.allowedRuntimeOrigins)
37
- ? options.allowedRuntimeOrigins.filter((o) => typeof o === 'string')
38
- : []
39
-
40
- return {
41
- language: options.language || 'vi',
42
- theme: options.theme ?? 'auto',
43
- themeToggle: options.themeToggle,
44
- openAppStoreOnMount: options.openAppStoreOnMount !== false,
45
- allowedRuntimeOrigins: origins,
46
- coreUrl: options.coreUrl || '',
47
- backendUrl: (options.backendUrl || '').replace(/\/$/, ''),
48
- hasToken: !!(options.token),
49
- allowSameOriginEmbed: options.allowSameOriginEmbed === true,
50
- allowUnsafeOrigin: options.allowUnsafeOrigin === true,
51
- hubOrigin: typeof options.hubOrigin === 'string' ? options.hubOrigin.trim() : '',
52
- runtimePublicUrl: typeof options.runtimePublicUrl === 'string' ? options.runtimePublicUrl.trim() : '',
53
- enforceDedicatedHubOrigin: options.enforceDedicatedHubOrigin !== false,
54
- enforceIsolatedHostedRuntime: options.enforceIsolatedHostedRuntime !== false,
55
- allowSameOriginHostedRuntime: options.allowSameOriginHostedRuntime === true,
56
- hostedSandboxSameOrigin: options.hostedSandboxSameOrigin === true,
57
- enforceDevFriendlyOrigins: typeof options.enforceDevFriendlyOrigins === 'boolean'
58
- ? options.enforceDevFriendlyOrigins
59
- : true,
60
- isDevUser: options.isDevUser === true,
61
- serverHubPublicUrl: typeof options.serverHubPublicUrl === 'string' ? options.serverHubPublicUrl.trim() : '',
62
- serverFrontendOrigin: typeof options.serverFrontendOrigin === 'string' ? options.serverFrontendOrigin.trim() : '',
63
- serverRuntimePublicUrl: typeof options.serverRuntimePublicUrl === 'string' ? options.serverRuntimePublicUrl.trim() : '',
64
- serverOriginsAuto: options.serverOriginsAuto === true,
65
- serverOriginsResolved: options.serverOriginsResolved === true,
66
- originBootstrapLoading: options.originBootstrapLoading === true,
67
- originBlocked: false,
68
- originCheckPending: false,
69
- originBlockReason: null,
70
- originBlockParentOrigin: null,
71
- originBlockExpectedHubOrigin: null,
72
- originBlockExpectedRuntimeOrigin: null,
73
- }
74
- }
75
-
76
- /** @param {Record<string, unknown>} target */
77
- function applyOriginSafety(target, sourceOptions = {}) {
78
- const check = evaluateOriginSafety({ ...target, ...sourceOptions })
79
- target.originBootstrapLoading = check.loading === true
80
- target.originCheckPending = check.pending === true
81
- target.originBlocked = !check.safe && check.loading !== true
82
- target.originBlockReason = check.reason
83
- target.originBlockParentOrigin = check.parentOrigin
84
- target.originBlockExpectedHubOrigin = check.expectedHubOrigin
85
- target.originBlockExpectedRuntimeOrigin = check.expectedRuntimeOrigin
86
- return check
87
- }
88
-
89
- function parseUserFromBootstrap(resp) {
90
- const data = resp?.data?.data ?? resp?.data ?? {}
91
- const user = data.user
92
- if (!user || user.id == null) return null
93
- return {
94
- id: user.id,
95
- name: user.name ?? String(user.id),
96
- }
97
- }
98
-
99
- function applyBootstrapOrigins(store, bootstrapResponse, { fromCache = false } = {}) {
100
- const {
101
- hubPublicUrl,
102
- frontendOrigin,
103
- runtimePublicUrl,
104
- originsAuto,
105
- } = parseOriginsFromBootstrap(bootstrapResponse)
106
-
107
- store.options.isDevUser = parseDevUserFromBootstrap(bootstrapResponse)
108
- store.options.serverOriginsAuto = originsAuto
109
-
110
- if (!store.options.isDevUser) {
111
- store.options.enforceDevFriendlyOrigins = true
112
- } else {
113
- store.options.enforceDevFriendlyOrigins = loadDevFriendlyOriginsPreference()
114
- }
115
-
116
- if (hubPublicUrl) {
117
- store.options.serverHubPublicUrl = hubPublicUrl
118
- }
119
- if (frontendOrigin) {
120
- store.options.serverFrontendOrigin = frontendOrigin
121
- }
122
- if (runtimePublicUrl) {
123
- store.options.serverRuntimePublicUrl = runtimePublicUrl
124
- if (!store.options.runtimePublicUrl) store.options.runtimePublicUrl = runtimePublicUrl
125
- }
126
-
127
- store.options.serverOriginsResolved = true
128
-
129
- if (!fromCache) {
130
- saveBootstrapCache(store.credentials.backendUrl, bootstrapResponse)
131
- }
132
-
133
- const user = parseUserFromBootstrap(bootstrapResponse)
134
- if (user && store.zoneContext?.state) {
135
- store.zoneContext.state.user.id = user.id
136
- store.zoneContext.state.user.name = user.name
137
- }
138
-
139
- // Reconcile while originBootstrapLoading may still be true so enableModuleApi runs
140
- // after disableModuleServices (wasLoading must be captured before clearing the flag).
141
- reconcileOriginSafety(store)
142
- store.options.originBootstrapLoading = false
143
- applyOriginSafety(store.options)
144
- apphubDebug('bootstrap', 'applyBootstrapOrigins done', describeStore(store))
145
- }
146
-
147
- function applyCachedBootstrapIfAny(store) {
148
- const cached = loadBootstrapCache(store.credentials.backendUrl)
149
- if (!cached) return false
150
- applyBootstrapOrigins(store, bootstrapResponseFromCache(cached), { fromCache: true })
151
- return true
152
- }
153
-
154
- async function fetchBootstrapSession(store) {
155
- let promise = bootstrapInflight.get(store)
156
- if (promise) return promise
157
-
158
- promise = (async () => {
159
- apphubDebug('bootstrap', 'fetchBootstrapSession start', describeStore(store))
160
- syncApi(store)
161
- if (!store.facade?.bootstrap) {
162
- apphubDebug('bootstrap', 'fetchBootstrapSession abort — no bootstrap fn', describeStore(store))
163
- return
164
- }
165
-
166
- const res = await store.facade.bootstrap()
167
- apphubDebug('bootstrap', 'fetchBootstrapSession bootstrap response', {
168
- ...describeStore(store),
169
- hasResponse: !!res,
170
- })
171
- if (res) applyBootstrapOrigins(store, res)
172
- })().catch((err) => {
173
- apphubDebug('bootstrap', 'fetchBootstrapSession failed', {
174
- ...describeStore(store),
175
- error: err?.message ?? String(err),
176
- })
177
- store.options.originBootstrapLoading = false
178
- if (!store.options.serverOriginsResolved) {
179
- reconcileOriginSafety(store)
180
- }
181
- }).finally(() => {
182
- bootstrapInflight.delete(store)
183
- })
184
-
185
- bootstrapInflight.set(store, promise)
186
- return promise
187
- }
188
-
189
- function startBootstrapSession(store) {
190
- const { backendUrl, token } = store.credentials
191
- apphubDebug('bootstrap', 'startBootstrapSession', {
192
- ...describeStore(store),
193
- hasBackendUrl: !!backendUrl,
194
- hasToken: !!token,
195
- })
196
- if (!backendUrl || !token) {
197
- store.options.originBootstrapLoading = false
198
- reconcileOriginSafety(store)
199
- return Promise.resolve()
200
- }
201
-
202
- syncApi(store)
203
- applyOriginSafety(store.options)
204
-
205
- const hadCache = applyCachedBootstrapIfAny(store)
206
- apphubDebug('bootstrap', 'startBootstrapSession after cache', {
207
- hadCache,
208
- ...describeStore(store),
209
- })
210
- if (!hadCache && !store.options.originBlocked) {
211
- store.options.originBootstrapLoading = true
212
- store.options.originBlocked = false
213
- applyOriginSafety(store.options)
214
- disableModuleServices(store, 'startBootstrapSession:no-cache')
215
- }
216
-
217
- return fetchBootstrapSession(store)
218
- }
219
-
220
- function reconcileOriginSafety(store) {
221
- const wasBlocked = store.options.originBlocked === true
222
- const wasLoading = store.options.originBootstrapLoading === true
223
- applyOriginSafety(store.options)
224
-
225
- let action = 'noop'
226
- if (store.options.originBootstrapLoading) {
227
- disableModuleServices(store, 'reconcileOriginSafety:loading')
228
- action = 'disable:loading'
229
- } else if (store.options.originBlocked && !wasBlocked) {
230
- disableModuleServices(store, 'reconcileOriginSafety:blocked')
231
- action = 'disable:blocked'
232
- } else if (!store.options.originBlocked && (wasBlocked || wasLoading)) {
233
- enableModuleApi(store)
234
- action = 'enable'
235
- }
236
-
237
- apphubDebug('origin', 'reconcileOriginSafety', {
238
- wasLoading,
239
- wasBlocked,
240
- action,
241
- ...describeStore(store),
242
- })
243
- }
244
-
245
- function disableModuleServices(store, reason = 'unknown') {
246
- store.facade.setImpl(null)
247
- store.coreApi = null
248
- apphubDebug('api', 'disableModuleServices', { reason, ...describeStore(store) })
249
- }
250
-
251
- /** Window manager + app store — required even when origin is blocked (AppHubDesktop setup). */
252
- function ensureModuleInfrastructure(vueApp, store) {
253
- ensureZoneContext(vueApp, store)
254
- ensureModuleState(vueApp, store)
255
- }
256
-
257
- function enableModuleApi(store) {
258
- syncApi(store)
259
- syncZoneContext(store)
260
- apphubDebug('api', 'enableModuleApi', describeStore(store))
261
- }
262
-
263
- function enableModuleServices(vueApp, store) {
264
- ensureModuleInfrastructure(vueApp, store)
265
- enableModuleApi(store)
266
- }
267
-
268
- function buildCredentials(options = {}) {
269
- return {
270
- coreUrl: options.coreUrl || '',
271
- backendUrl: options.backendUrl || '',
272
- token: options.token || '',
273
- hostAccessSecret: options.hostAccessSecret || '',
274
- }
275
- }
276
-
277
- function createApiFacade() {
278
- let impl = null
279
- return {
280
- setImpl(next) {
281
- impl = next
282
- },
283
- hasImpl() {
284
- return impl != null
285
- },
286
- bootstrap: (...args) => impl?.bootstrap?.(...args),
287
- apps: (...args) => impl?.apps?.(...args),
288
- launch: (...args) => impl?.launch?.(...args),
289
- ping: (...args) => impl?.ping?.(...args),
290
- verifyLaunchToken: (...args) => impl?.verifyLaunchToken?.(...args),
291
- usage: (...args) => impl?.usage?.(...args),
292
- devApps: (...args) => impl?.devApps?.(...args),
293
- devInspectBundle: (...args) => impl?.devInspectBundle?.(...args),
294
- devDisableApp: (...args) => impl?.devDisableApp?.(...args),
295
- devSetAppStatus: (...args) => impl?.devSetAppStatus?.(...args),
296
- registerApp: (...args) => impl?.registerApp?.(...args),
297
- appVersions: (...args) => impl?.appVersions?.(...args),
298
- integrationDocs: (...args) => impl?.integrationDocs?.(...args),
299
- integrationDocsInternal: (...args) => impl?.integrationDocsInternal?.(...args),
300
- grantBridgeScope: (...args) => impl?.grantBridgeScope?.(...args),
301
- bridgeUser: (...args) => impl?.bridgeUser?.(...args),
302
- bridgeDesktopMessage: (...args) => impl?.bridgeDesktopMessage?.(...args),
303
- }
304
- }
305
-
306
- function credentialsUnchanged(prev, next) {
307
- return prev.backendUrl === next.backendUrl
308
- && prev.token === next.token
309
- && prev.coreUrl === next.coreUrl
310
- && prev.hostAccessSecret === next.hostAccessSecret
311
- }
312
-
313
- function applyModuleOptions(store, options = {}) {
314
- const prevCredentials = { ...store.credentials }
315
- const nextCredentials = buildCredentials(options)
316
- Object.assign(store.credentials, nextCredentials)
317
- const credsUnchanged = credentialsUnchanged(prevCredentials, nextCredentials)
318
-
319
- const nextPublic = buildPublicOptions({ ...options, token: options.token ?? store.credentials.token })
320
- Object.assign(store.options, {
321
- language: nextPublic.language,
322
- theme: nextPublic.theme,
323
- themeToggle: nextPublic.themeToggle,
324
- openAppStoreOnMount: nextPublic.openAppStoreOnMount,
325
- allowedRuntimeOrigins: nextPublic.allowedRuntimeOrigins,
326
- coreUrl: nextPublic.coreUrl,
327
- backendUrl: nextPublic.backendUrl,
328
- hasToken: nextPublic.hasToken,
329
- allowSameOriginEmbed: nextPublic.allowSameOriginEmbed,
330
- allowUnsafeOrigin: nextPublic.allowUnsafeOrigin,
331
- hubOrigin: nextPublic.hubOrigin,
332
- runtimePublicUrl: nextPublic.runtimePublicUrl,
333
- enforceDedicatedHubOrigin: nextPublic.enforceDedicatedHubOrigin,
334
- enforceIsolatedHostedRuntime: nextPublic.enforceIsolatedHostedRuntime,
335
- allowSameOriginHostedRuntime: nextPublic.allowSameOriginHostedRuntime,
336
- hostedSandboxSameOrigin: nextPublic.hostedSandboxSameOrigin,
337
- enforceDevFriendlyOrigins: nextPublic.enforceDevFriendlyOrigins,
338
- })
339
- applyOriginSafety(store.options, options)
340
- const branch = !store.credentials.backendUrl || !store.credentials.token
341
- ? 'no-credentials'
342
- : (credsUnchanged ? 'creds-unchanged' : 'start-bootstrap')
343
- apphubDebug('install', 'applyModuleOptions', {
344
- branch,
345
- credsUnchanged,
346
- ...describeStore(store),
347
- })
348
- if (store.credentials.backendUrl && store.credentials.token) {
349
- if (credsUnchanged) {
350
- reconcileOriginSafety(store)
351
- } else {
352
- void startBootstrapSession(store)
353
- }
354
- } else {
355
- reconcileOriginSafety(store)
356
- }
357
- }
358
-
359
- function syncApi(store) {
360
- const { credentials, facade, zoneContext } = store
361
- const api = credentials.backendUrl && credentials.token
362
- ? createAppHubApi(credentials.backendUrl, credentials.token, {
363
- hostAccessSecret: credentials.hostAccessSecret,
364
- getZoneHeaderId: () => zoneContext?.getZoneHeaderId?.() ?? null,
365
- })
366
- : null
367
- facade.setImpl(api)
368
- apphubDebug('api', 'syncApi', {
369
- implSet: api != null,
370
- ...describeStore(store),
371
- })
372
- }
373
-
374
- function syncCoreApi(store) {
375
- const { credentials } = store
376
- store.coreApi = credentials.coreUrl && credentials.token
377
- ? createCoreApi(credentials.coreUrl, credentials.token)
378
- : null
379
- }
380
-
381
- function ensureZoneContext(app, store) {
382
- if (store.zoneContext) return
383
- store.zoneContext = createZoneContextState(
384
- () => store.coreApi,
385
- () => store.facade,
386
- {
387
- ensureBootstrapSession: () => fetchBootstrapSession(store),
388
- },
389
- )
390
- app.provide(APPHUB_ZONE_CONTEXT_KEY, store.zoneContext)
391
- }
392
-
393
- function syncZoneContext(store) {
394
- syncCoreApi(store)
395
- if (store.zoneContext) {
396
- store.zoneContext.refresh({ skipBootstrap: true })
397
- }
398
- }
399
-
400
- function ensureModuleState(app, store) {
401
- if (store.windowManager && store.appStore) {
402
- return
403
- }
404
-
405
- store.windowManager = createWindowManagerState()
406
- store.appStore = createAppStoreState()
407
- provideWindowManager(app, store.windowManager)
408
- provideAppStore(app, store.appStore)
409
- }
410
-
411
- /**
412
- * Install App Hub Windows desktop shell + modular apps (App Store default).
413
- * Returns host API facade — keep in host app code, not publisher apps.
414
- */
415
- export function installAppHubModule(vueApp, options = {}) {
416
- let store = getAppHubStore(vueApp)
417
-
418
- if (store) {
419
- vueApp.provide('apphubHostApp', vueApp)
420
- vueApp.provide(APPHUB_MODULE_STORE_KEY, store)
421
- apphubDebug('install', 'installAppHubModule re-install', {
422
- appUid: vueApp._uid ?? null,
423
- hasToken: !!(options.token),
424
- theme: options.theme,
425
- language: options.language,
426
- })
427
- applyModuleOptions(store, options)
428
- ensureModuleInfrastructure(vueApp, store)
429
- reconcileOriginSafety(store)
430
- return store.facade
431
- }
432
-
433
- apphubDebug('install', 'installAppHubModule first install', {
434
- appUid: vueApp._uid ?? null,
435
- hasToken: !!(options.token),
436
- backendUrl: options.backendUrl ? '(set)' : '(empty)',
437
- })
438
-
439
- const facade = createApiFacade()
440
- const moduleOptions = reactive(buildPublicOptions(options))
441
- applyOriginSafety(moduleOptions, options)
442
- const credentials = buildCredentials(options)
443
-
444
- store = {
445
- app: vueApp,
446
- facade,
447
- options: moduleOptions,
448
- credentials,
449
- coreApi: null,
450
- zoneContext: null,
451
- windowManager: null,
452
- appStore: null,
453
- }
454
- registerAppHubStore(vueApp, store)
455
-
456
- vueApp.provide('apphubOptions', moduleOptions)
457
- vueApp.provide('apphubHostApp', vueApp)
458
- vueApp.provide(APPHUB_MODULE_STORE_KEY, store)
459
- vueApp.component('AppHubDesktop', AppHubDesktop)
460
- vueApp.component('AppHubRunner', AppHubRunner)
461
-
462
- ensureModuleInfrastructure(vueApp, store)
463
- void startBootstrapSession(store)
464
-
465
- return facade
466
- }
467
-
468
- /** Whether installAppHubModule has been called for this Vue app instance. */
469
- export function isAppHubModuleInstalled(vueApp) {
470
- return vueApp != null && getAppHubStore(vueApp) != null
471
- }
472
-
473
- export { useAppHubHostApi, useAppHubModuleStore } from './composables/useAppHubHostApi.js'
474
- export { useAppHubZoneContext } from './composables/useAppHubZoneContext.js'
475
- export { createAppHubApi } from './api/index.js'
476
- export { createCoreApi } from './api/coreApi.js'
477
- export { AppHubDesktop } from './modules/desktop/index.js'
478
- export {
479
- createDesktopNotificationsState,
480
- useDesktopNotifications,
481
- AppHubDesktopNotifications,
482
- parseApiError,
483
- } from './modules/notifications/index.js'
484
- export { AppHubRunner } from './modules/runner/index.js'
485
- export { resolveLang } from './i18n/resolveLang.js'
486
- export { resolveTheme, normalizeTheme, isThemeLocked } from './i18n/resolveTheme.js'
487
- export { t } from './i18n/index.js'
488
- export {
489
- evaluateOriginSafety,
490
- isEmbeddedFrame,
491
- isLocalDevOrigin,
492
- parseOriginsFromBootstrap,
493
- resolveEffectiveOrigins,
494
- ORIGIN_UNSAFE_SAME_ORIGIN_EMBED,
495
- ORIGIN_UNSAFE_NOT_CONFIGURED,
496
- ORIGIN_UNSAFE_WRONG_ORIGIN,
497
- ORIGIN_UNSAFE_RUNTIME_NOT_CONFIGURED,
498
- ORIGIN_UNSAFE_RUNTIME_SAME_ORIGIN,
499
- resolveRuntimeApiBase,
500
- } from './utils/originSafety.js'
501
-
502
- /** True when installAppHubModule blocked due to unsafe same-origin embed. */
503
- export function isAppHubOriginBlocked(vueApp) {
504
- const store = getAppHubStore(vueApp)
505
- return store?.options?.originBlocked === true
506
- }
507
- export * from './modules/app-store/index.js'
508
- export * from './modules/window-manager/index.js'
509
- export * from './modules/runner/index.js'
1
+ import './modules/desktop/styles/desktop.css'
2
+ import './modules/desktop/styles/theme.css'
3
+ import { reactive } from 'vue'
4
+ import { createAppHubApi } from './api/index.js'
5
+ import { createCoreApi } from './api/coreApi.js'
6
+ import { createZoneContextState } from './composables/createZoneContext.js'
7
+ import { APPHUB_ZONE_CONTEXT_KEY } from './composables/useAppHubZoneContext.js'
8
+ import { createAppStoreState, provideAppStore } from './modules/app-store/index.js'
9
+ import { AppHubDesktop } from './modules/desktop/index.js'
10
+ import { AppHubRunner } from './modules/runner/index.js'
11
+ import { getAppHubStore, registerAppHubStore, APPHUB_MODULE_STORE_KEY } from './moduleStore.js'
12
+ import {
13
+ createWindowManagerState,
14
+ provideWindowManager,
15
+ } from './modules/window-manager/index.js'
16
+ import {
17
+ evaluateOriginSafety,
18
+ parseDevUserFromBootstrap,
19
+ parseOriginsFromBootstrap,
20
+ } from './utils/originSafety.js'
21
+ import { loadDevFriendlyOriginsPreference } from './utils/devOriginSettings.js'
22
+ import {
23
+ bootstrapResponseFromCache,
24
+ loadBootstrapCache,
25
+ saveBootstrapCache,
26
+ } from './utils/bootstrapCache.js'
27
+
28
+ const bootstrapInflight = new WeakMap()
29
+
30
+ function buildPublicOptions(options = {}) {
31
+ const origins = Array.isArray(options.allowedRuntimeOrigins)
32
+ ? options.allowedRuntimeOrigins.filter((o) => typeof o === 'string')
33
+ : []
34
+
35
+ return {
36
+ language: options.language || 'vi',
37
+ theme: options.theme ?? 'auto',
38
+ themeToggle: options.themeToggle,
39
+ openAppStoreOnMount: options.openAppStoreOnMount !== false,
40
+ allowedRuntimeOrigins: origins,
41
+ coreUrl: options.coreUrl || '',
42
+ backendUrl: (options.backendUrl || '').replace(/\/$/, ''),
43
+ hasToken: !!(options.token),
44
+ allowSameOriginEmbed: options.allowSameOriginEmbed === true,
45
+ allowUnsafeOrigin: options.allowUnsafeOrigin === true,
46
+ hubOrigin: typeof options.hubOrigin === 'string' ? options.hubOrigin.trim() : '',
47
+ runtimePublicUrl: typeof options.runtimePublicUrl === 'string' ? options.runtimePublicUrl.trim() : '',
48
+ enforceDedicatedHubOrigin: options.enforceDedicatedHubOrigin !== false,
49
+ enforceIsolatedHostedRuntime: options.enforceIsolatedHostedRuntime !== false,
50
+ allowSameOriginHostedRuntime: options.allowSameOriginHostedRuntime === true,
51
+ hostedSandboxSameOrigin: options.hostedSandboxSameOrigin === true,
52
+ enforceDevFriendlyOrigins: typeof options.enforceDevFriendlyOrigins === 'boolean'
53
+ ? options.enforceDevFriendlyOrigins
54
+ : true,
55
+ isDevUser: options.isDevUser === true,
56
+ serverHubPublicUrl: typeof options.serverHubPublicUrl === 'string' ? options.serverHubPublicUrl.trim() : '',
57
+ serverFrontendOrigin: typeof options.serverFrontendOrigin === 'string' ? options.serverFrontendOrigin.trim() : '',
58
+ serverRuntimePublicUrl: typeof options.serverRuntimePublicUrl === 'string' ? options.serverRuntimePublicUrl.trim() : '',
59
+ serverOriginsAuto: options.serverOriginsAuto === true,
60
+ serverOriginsResolved: options.serverOriginsResolved === true,
61
+ originBootstrapLoading: options.originBootstrapLoading === true,
62
+ originBlocked: false,
63
+ originCheckPending: false,
64
+ originBlockReason: null,
65
+ originBlockParentOrigin: null,
66
+ originBlockExpectedHubOrigin: null,
67
+ originBlockExpectedRuntimeOrigin: null,
68
+ }
69
+ }
70
+
71
+ /** @param {Record<string, unknown>} target */
72
+ function applyOriginSafety(target, sourceOptions = {}) {
73
+ const check = evaluateOriginSafety({ ...target, ...sourceOptions })
74
+ target.originBootstrapLoading = check.loading === true
75
+ target.originCheckPending = check.pending === true
76
+ target.originBlocked = !check.safe && check.loading !== true
77
+ target.originBlockReason = check.reason
78
+ target.originBlockParentOrigin = check.parentOrigin
79
+ target.originBlockExpectedHubOrigin = check.expectedHubOrigin
80
+ target.originBlockExpectedRuntimeOrigin = check.expectedRuntimeOrigin
81
+ return check
82
+ }
83
+
84
+ function parseUserFromBootstrap(resp) {
85
+ const data = resp?.data?.data ?? resp?.data ?? {}
86
+ const user = data.user
87
+ if (!user || user.id == null) return null
88
+ return {
89
+ id: user.id,
90
+ name: user.name ?? String(user.id),
91
+ }
92
+ }
93
+
94
+ function applyBootstrapOrigins(store, bootstrapResponse, { fromCache = false } = {}) {
95
+ const {
96
+ hubPublicUrl,
97
+ frontendOrigin,
98
+ runtimePublicUrl,
99
+ originsAuto,
100
+ } = parseOriginsFromBootstrap(bootstrapResponse)
101
+
102
+ store.options.isDevUser = parseDevUserFromBootstrap(bootstrapResponse)
103
+ store.options.serverOriginsAuto = originsAuto
104
+
105
+ if (!store.options.isDevUser) {
106
+ store.options.enforceDevFriendlyOrigins = true
107
+ } else {
108
+ store.options.enforceDevFriendlyOrigins = loadDevFriendlyOriginsPreference()
109
+ }
110
+
111
+ if (hubPublicUrl) {
112
+ store.options.serverHubPublicUrl = hubPublicUrl
113
+ }
114
+ if (frontendOrigin) {
115
+ store.options.serverFrontendOrigin = frontendOrigin
116
+ }
117
+ if (runtimePublicUrl) {
118
+ store.options.serverRuntimePublicUrl = runtimePublicUrl
119
+ if (!store.options.runtimePublicUrl) store.options.runtimePublicUrl = runtimePublicUrl
120
+ }
121
+
122
+ store.options.serverOriginsResolved = true
123
+
124
+ if (!fromCache) {
125
+ saveBootstrapCache(store.credentials.backendUrl, bootstrapResponse)
126
+ }
127
+
128
+ const user = parseUserFromBootstrap(bootstrapResponse)
129
+ if (user && store.zoneContext?.state) {
130
+ store.zoneContext.state.user.id = user.id
131
+ store.zoneContext.state.user.name = user.name
132
+ }
133
+
134
+ // Reconcile while originBootstrapLoading may still be true so enableModuleApi runs
135
+ // after disableModuleServices (wasLoading must be captured before clearing the flag).
136
+ reconcileOriginSafety(store)
137
+ store.options.originBootstrapLoading = false
138
+ applyOriginSafety(store.options)
139
+ }
140
+
141
+ function applyCachedBootstrapIfAny(store) {
142
+ const cached = loadBootstrapCache(store.credentials.backendUrl)
143
+ if (!cached) return false
144
+ applyBootstrapOrigins(store, bootstrapResponseFromCache(cached), { fromCache: true })
145
+ return true
146
+ }
147
+
148
+ async function fetchBootstrapSession(store) {
149
+ let promise = bootstrapInflight.get(store)
150
+ if (promise) return promise
151
+
152
+ promise = (async () => {
153
+ syncApi(store)
154
+ if (!store.facade?.bootstrap) return
155
+
156
+ const res = await store.facade.bootstrap()
157
+ if (res) applyBootstrapOrigins(store, res)
158
+ })().catch(() => {
159
+ store.options.originBootstrapLoading = false
160
+ if (!store.options.serverOriginsResolved) {
161
+ reconcileOriginSafety(store)
162
+ }
163
+ }).finally(() => {
164
+ bootstrapInflight.delete(store)
165
+ })
166
+
167
+ bootstrapInflight.set(store, promise)
168
+ return promise
169
+ }
170
+
171
+ function startBootstrapSession(store) {
172
+ const { backendUrl, token } = store.credentials
173
+ if (!backendUrl || !token) {
174
+ store.options.originBootstrapLoading = false
175
+ reconcileOriginSafety(store)
176
+ return Promise.resolve()
177
+ }
178
+
179
+ syncApi(store)
180
+ applyOriginSafety(store.options)
181
+
182
+ const hadCache = applyCachedBootstrapIfAny(store)
183
+ if (!hadCache && !store.options.originBlocked) {
184
+ store.options.originBootstrapLoading = true
185
+ store.options.originBlocked = false
186
+ applyOriginSafety(store.options)
187
+ disableModuleServices(store)
188
+ }
189
+
190
+ return fetchBootstrapSession(store)
191
+ }
192
+
193
+ function reconcileOriginSafety(store) {
194
+ const wasBlocked = store.options.originBlocked === true
195
+ const wasLoading = store.options.originBootstrapLoading === true
196
+ applyOriginSafety(store.options)
197
+
198
+ if (store.options.originBootstrapLoading) {
199
+ disableModuleServices(store)
200
+ return
201
+ }
202
+
203
+ if (store.options.originBlocked && !wasBlocked) {
204
+ disableModuleServices(store)
205
+ } else if (!store.options.originBlocked && (wasBlocked || wasLoading)) {
206
+ enableModuleApi(store)
207
+ }
208
+ }
209
+
210
+ function disableModuleServices(store) {
211
+ store.facade.setImpl(null)
212
+ store.coreApi = null
213
+ }
214
+
215
+ /** Window manager + app store — required even when origin is blocked (AppHubDesktop setup). */
216
+ function ensureModuleInfrastructure(vueApp, store) {
217
+ ensureZoneContext(vueApp, store)
218
+ ensureModuleState(vueApp, store)
219
+ }
220
+
221
+ function enableModuleApi(store) {
222
+ syncApi(store)
223
+ syncZoneContext(store)
224
+ }
225
+
226
+ function enableModuleServices(vueApp, store) {
227
+ ensureModuleInfrastructure(vueApp, store)
228
+ enableModuleApi(store)
229
+ }
230
+
231
+ function buildCredentials(options = {}) {
232
+ return {
233
+ coreUrl: options.coreUrl || '',
234
+ backendUrl: options.backendUrl || '',
235
+ token: options.token || '',
236
+ hostAccessSecret: options.hostAccessSecret || '',
237
+ }
238
+ }
239
+
240
+ function createApiFacade() {
241
+ let impl = null
242
+ return {
243
+ setImpl(next) {
244
+ impl = next
245
+ },
246
+ hasImpl() {
247
+ return impl != null
248
+ },
249
+ bootstrap: (...args) => impl?.bootstrap?.(...args),
250
+ apps: (...args) => impl?.apps?.(...args),
251
+ launch: (...args) => impl?.launch?.(...args),
252
+ ping: (...args) => impl?.ping?.(...args),
253
+ verifyLaunchToken: (...args) => impl?.verifyLaunchToken?.(...args),
254
+ usage: (...args) => impl?.usage?.(...args),
255
+ devApps: (...args) => impl?.devApps?.(...args),
256
+ devInspectBundle: (...args) => impl?.devInspectBundle?.(...args),
257
+ devDisableApp: (...args) => impl?.devDisableApp?.(...args),
258
+ devSetAppStatus: (...args) => impl?.devSetAppStatus?.(...args),
259
+ registerApp: (...args) => impl?.registerApp?.(...args),
260
+ appVersions: (...args) => impl?.appVersions?.(...args),
261
+ integrationDocs: (...args) => impl?.integrationDocs?.(...args),
262
+ integrationDocsInternal: (...args) => impl?.integrationDocsInternal?.(...args),
263
+ grantBridgeScope: (...args) => impl?.grantBridgeScope?.(...args),
264
+ bridgeUser: (...args) => impl?.bridgeUser?.(...args),
265
+ bridgeDesktopMessage: (...args) => impl?.bridgeDesktopMessage?.(...args),
266
+ }
267
+ }
268
+
269
+ function credentialsUnchanged(prev, next) {
270
+ return prev.backendUrl === next.backendUrl
271
+ && prev.token === next.token
272
+ && prev.coreUrl === next.coreUrl
273
+ && prev.hostAccessSecret === next.hostAccessSecret
274
+ }
275
+
276
+ function applyModuleOptions(store, options = {}) {
277
+ const prevCredentials = { ...store.credentials }
278
+ const nextCredentials = buildCredentials(options)
279
+ Object.assign(store.credentials, nextCredentials)
280
+ const credsUnchanged = credentialsUnchanged(prevCredentials, nextCredentials)
281
+
282
+ const nextPublic = buildPublicOptions({ ...options, token: options.token ?? store.credentials.token })
283
+ Object.assign(store.options, {
284
+ language: nextPublic.language,
285
+ theme: nextPublic.theme,
286
+ themeToggle: nextPublic.themeToggle,
287
+ openAppStoreOnMount: nextPublic.openAppStoreOnMount,
288
+ allowedRuntimeOrigins: nextPublic.allowedRuntimeOrigins,
289
+ coreUrl: nextPublic.coreUrl,
290
+ backendUrl: nextPublic.backendUrl,
291
+ hasToken: nextPublic.hasToken,
292
+ allowSameOriginEmbed: nextPublic.allowSameOriginEmbed,
293
+ allowUnsafeOrigin: nextPublic.allowUnsafeOrigin,
294
+ hubOrigin: nextPublic.hubOrigin,
295
+ runtimePublicUrl: nextPublic.runtimePublicUrl,
296
+ enforceDedicatedHubOrigin: nextPublic.enforceDedicatedHubOrigin,
297
+ enforceIsolatedHostedRuntime: nextPublic.enforceIsolatedHostedRuntime,
298
+ allowSameOriginHostedRuntime: nextPublic.allowSameOriginHostedRuntime,
299
+ hostedSandboxSameOrigin: nextPublic.hostedSandboxSameOrigin,
300
+ enforceDevFriendlyOrigins: nextPublic.enforceDevFriendlyOrigins,
301
+ })
302
+ applyOriginSafety(store.options, options)
303
+ if (store.credentials.backendUrl && store.credentials.token) {
304
+ if (credsUnchanged) {
305
+ reconcileOriginSafety(store)
306
+ } else {
307
+ void startBootstrapSession(store)
308
+ }
309
+ } else {
310
+ reconcileOriginSafety(store)
311
+ }
312
+ }
313
+
314
+ function syncApi(store) {
315
+ const { credentials, facade, zoneContext } = store
316
+ const api = credentials.backendUrl && credentials.token
317
+ ? createAppHubApi(credentials.backendUrl, credentials.token, {
318
+ hostAccessSecret: credentials.hostAccessSecret,
319
+ getZoneHeaderId: () => zoneContext?.getZoneHeaderId?.() ?? null,
320
+ })
321
+ : null
322
+ facade.setImpl(api)
323
+ }
324
+
325
+ function syncCoreApi(store) {
326
+ const { credentials } = store
327
+ store.coreApi = credentials.coreUrl && credentials.token
328
+ ? createCoreApi(credentials.coreUrl, credentials.token)
329
+ : null
330
+ }
331
+
332
+ function ensureZoneContext(app, store) {
333
+ if (store.zoneContext) return
334
+ store.zoneContext = createZoneContextState(
335
+ () => store.coreApi,
336
+ () => store.facade,
337
+ {
338
+ ensureBootstrapSession: () => fetchBootstrapSession(store),
339
+ },
340
+ )
341
+ app.provide(APPHUB_ZONE_CONTEXT_KEY, store.zoneContext)
342
+ }
343
+
344
+ function syncZoneContext(store) {
345
+ syncCoreApi(store)
346
+ if (store.zoneContext) {
347
+ store.zoneContext.refresh({ skipBootstrap: true })
348
+ }
349
+ }
350
+
351
+ function ensureModuleState(app, store) {
352
+ if (store.windowManager && store.appStore) {
353
+ return
354
+ }
355
+
356
+ store.windowManager = createWindowManagerState()
357
+ store.appStore = createAppStoreState()
358
+ provideWindowManager(app, store.windowManager)
359
+ provideAppStore(app, store.appStore)
360
+ }
361
+
362
+ /**
363
+ * Install App Hub — Windows desktop shell + modular apps (App Store default).
364
+ * Returns host API facade — keep in host app code, not publisher apps.
365
+ */
366
+ export function installAppHubModule(vueApp, options = {}) {
367
+ let store = getAppHubStore(vueApp)
368
+
369
+ if (store) {
370
+ vueApp.provide('apphubHostApp', vueApp)
371
+ vueApp.provide(APPHUB_MODULE_STORE_KEY, store)
372
+ applyModuleOptions(store, options)
373
+ ensureModuleInfrastructure(vueApp, store)
374
+ reconcileOriginSafety(store)
375
+ return store.facade
376
+ }
377
+
378
+ const facade = createApiFacade()
379
+ const moduleOptions = reactive(buildPublicOptions(options))
380
+ applyOriginSafety(moduleOptions, options)
381
+ const credentials = buildCredentials(options)
382
+
383
+ store = {
384
+ app: vueApp,
385
+ facade,
386
+ options: moduleOptions,
387
+ credentials,
388
+ coreApi: null,
389
+ zoneContext: null,
390
+ windowManager: null,
391
+ appStore: null,
392
+ }
393
+ registerAppHubStore(vueApp, store)
394
+
395
+ vueApp.provide('apphubOptions', moduleOptions)
396
+ vueApp.provide('apphubHostApp', vueApp)
397
+ vueApp.provide(APPHUB_MODULE_STORE_KEY, store)
398
+ vueApp.component('AppHubDesktop', AppHubDesktop)
399
+ vueApp.component('AppHubRunner', AppHubRunner)
400
+
401
+ ensureModuleInfrastructure(vueApp, store)
402
+ void startBootstrapSession(store)
403
+
404
+ return facade
405
+ }
406
+
407
+ /** Whether installAppHubModule has been called for this Vue app instance. */
408
+ export function isAppHubModuleInstalled(vueApp) {
409
+ return vueApp != null && getAppHubStore(vueApp) != null
410
+ }
411
+
412
+ export { useAppHubHostApi, useAppHubModuleStore } from './composables/useAppHubHostApi.js'
413
+ export { useAppHubZoneContext } from './composables/useAppHubZoneContext.js'
414
+ export { createAppHubApi } from './api/index.js'
415
+ export { createCoreApi } from './api/coreApi.js'
416
+ export { AppHubDesktop } from './modules/desktop/index.js'
417
+ export {
418
+ createDesktopNotificationsState,
419
+ useDesktopNotifications,
420
+ AppHubDesktopNotifications,
421
+ parseApiError,
422
+ } from './modules/notifications/index.js'
423
+ export { AppHubRunner } from './modules/runner/index.js'
424
+ export { resolveLang } from './i18n/resolveLang.js'
425
+ export { resolveTheme, normalizeTheme, isThemeLocked } from './i18n/resolveTheme.js'
426
+ export { t } from './i18n/index.js'
427
+ export {
428
+ evaluateOriginSafety,
429
+ isEmbeddedFrame,
430
+ isLocalDevOrigin,
431
+ parseOriginsFromBootstrap,
432
+ resolveEffectiveOrigins,
433
+ ORIGIN_UNSAFE_SAME_ORIGIN_EMBED,
434
+ ORIGIN_UNSAFE_NOT_CONFIGURED,
435
+ ORIGIN_UNSAFE_WRONG_ORIGIN,
436
+ ORIGIN_UNSAFE_RUNTIME_NOT_CONFIGURED,
437
+ ORIGIN_UNSAFE_RUNTIME_SAME_ORIGIN,
438
+ resolveRuntimeApiBase,
439
+ } from './utils/originSafety.js'
440
+
441
+ /** True when installAppHubModule blocked due to unsafe same-origin embed. */
442
+ export function isAppHubOriginBlocked(vueApp) {
443
+ const store = getAppHubStore(vueApp)
444
+ return store?.options?.originBlocked === true
445
+ }
446
+ export * from './modules/app-store/index.js'
447
+ export * from './modules/window-manager/index.js'
448
+ export * from './modules/runner/index.js'
449
+
@@ -109,7 +109,6 @@ import { resolveLang } from '../../../i18n/resolveLang.js'
109
109
  import { CATALOG_MODE_STORE } from '../constants/catalogModes.js'
110
110
  import { useCatalogInfiniteScroll } from '../composables/useCatalogInfiniteScroll.js'
111
111
  import { useAppStore } from '../composables/useAppStore.js'
112
- import { apphubDebug, describeHostApi } from '../../../utils/apphubDebug.js'
113
112
  import AppHubAppStoreCard from './AppHubAppStoreCard.vue'
114
113
  import AppHubAppStoreSettingsPanel from './AppHubAppStoreSettingsPanel.vue'
115
114
 
@@ -160,20 +159,8 @@ function hostApiOptions() {
160
159
  }
161
160
  }
162
161
 
163
- async function reloadCatalog(source = 'manual') {
164
- const api = hostApi
165
- const opts = hostApiOptions()
166
- apphubDebug('app-store', 'reloadCatalog', {
167
- source,
168
- storeFromInject: !!hubStore,
169
- ...describeHostApi(api),
170
- backendReady: opts.backendReady,
171
- originBootstrapLoading: moduleOptions?.originBootstrapLoading,
172
- originBlocked: moduleOptions?.originBlocked,
173
- catalogLoaded: catalog.loaded,
174
- catalogError: catalog.error,
175
- })
176
- await appStore.loadCatalog(api, opts)
162
+ async function reloadCatalog() {
163
+ await appStore.loadCatalog(hostApi, hostApiOptions())
177
164
  }
178
165
 
179
166
  async function loadMore() {
@@ -204,28 +191,21 @@ async function onUninstall(app) {
204
191
  }
205
192
 
206
193
  onMounted(() => {
207
- if (!catalog.loaded) reloadCatalog('onMounted')
194
+ if (!catalog.loaded) reloadCatalog()
208
195
  })
209
196
 
210
197
  watch(
211
198
  () => moduleOptions?.hasToken,
212
199
  (hasToken) => {
213
- if (hasToken && !catalog.loaded) reloadCatalog('watch:hasToken')
200
+ if (hasToken && !catalog.loaded) reloadCatalog()
214
201
  },
215
202
  )
216
203
 
217
204
  watch(
218
205
  () => moduleOptions?.originBootstrapLoading,
219
206
  (loading, wasLoading) => {
220
- apphubDebug('app-store', 'watch originBootstrapLoading', {
221
- loading,
222
- wasLoading,
223
- originBlocked: moduleOptions?.originBlocked,
224
- catalogLoaded: catalog.loaded,
225
- catalogError: catalog.error,
226
- })
227
207
  if (wasLoading && !loading && !moduleOptions?.originBlocked) {
228
- if (!catalog.loaded || catalog.error === 'no_api') reloadCatalog('watch:bootstrap-done')
208
+ if (!catalog.loaded || catalog.error === 'no_api') reloadCatalog()
229
209
  }
230
210
  },
231
211
  )
@@ -233,7 +213,7 @@ watch(
233
213
  watch(
234
214
  () => [zone?.state?.selectedZoneId, zone?.state?.viewAllZones],
235
215
  () => {
236
- if (moduleOptions?.hasToken) reloadCatalog('watch:zone')
216
+ if (moduleOptions?.hasToken) reloadCatalog()
237
217
  },
238
218
  )
239
219
  </script>
@@ -1,7 +1,6 @@
1
1
  import { computed, inject, reactive } from 'vue'
2
2
  import { CATALOG_MODE_DRAFT, CATALOG_MODE_STORE } from '../constants/catalogModes.js'
3
3
  import { normalizeCatalogList } from '../utils/normalizeCatalogApp.js'
4
- import { apphubDebug, describeHostApi } from '../../../utils/apphubDebug.js'
5
4
 
6
5
  const APP_STORE_KEY = 'apphubAppStore'
7
6
 
@@ -109,20 +108,7 @@ export function createAppStoreState(options = {}) {
109
108
  const append = options.append === true
110
109
  const backendReady = options.backendReady !== false
111
110
 
112
- apphubDebug('catalog', 'loadCatalog called', {
113
- mode,
114
- append,
115
- backendReady,
116
- ...describeHostApi(hostApi),
117
- })
118
-
119
111
  if (!backendReady || !hostApiReady(hostApi)) {
120
- apphubDebug('catalog', 'loadCatalog → no_api (not ready)', {
121
- mode,
122
- append,
123
- backendReady,
124
- ...describeHostApi(hostApi),
125
- })
126
112
  if (!append) {
127
113
  bucket.items = []
128
114
  bucket.error = 'no_api'
@@ -151,16 +137,8 @@ export function createAppStoreState(options = {}) {
151
137
  }
152
138
 
153
139
  const res = await hostApi.apps(params)
154
- apphubDebug('catalog', 'loadCatalog apps() response', {
155
- mode,
156
- append,
157
- ...describeHostApi(hostApi),
158
- resUndefined: res === undefined,
159
- resNull: res === null,
160
- })
161
140
  if (res === undefined || res === null) {
162
141
  if (!append) {
163
- apphubDebug('catalog', 'loadCatalog → no_api (undefined response)', { mode })
164
142
  bucket.items = []
165
143
  bucket.error = 'no_api'
166
144
  bucket.loaded = false
@@ -1,47 +0,0 @@
1
- /** Temporary diagnostics — filter console with `[apphub:debug]`. Disable: localStorage.setItem('apphub:debug','0') */
2
- export function isAppHubDebugEnabled() {
3
- try {
4
- if (typeof localStorage !== 'undefined' && localStorage.getItem('apphub:debug') === '0') {
5
- return false
6
- }
7
- if (typeof window !== 'undefined' && window.__APPHUB_DEBUG__ === false) {
8
- return false
9
- }
10
- } catch {
11
- /* ignore */
12
- }
13
- return true
14
- }
15
-
16
- export function apphubDebug(scope, message, data) {
17
- if (!isAppHubDebugEnabled()) return
18
- const payload = data !== undefined ? { ts: Date.now(), ...data } : { ts: Date.now() }
19
- console.info(`[apphub:debug] ${scope}: ${message}`, payload)
20
- }
21
-
22
- export function describeHostApi(hostApi) {
23
- if (!hostApi) return { hostApi: null }
24
- return {
25
- hasImpl: typeof hostApi.hasImpl === 'function' ? hostApi.hasImpl() : null,
26
- hasAppsFn: typeof hostApi.apps === 'function',
27
- }
28
- }
29
-
30
- export function describeStore(store) {
31
- if (!store) return { store: null }
32
- return {
33
- hasImpl: store.facade?.hasImpl?.() ?? null,
34
- originBootstrapLoading: store.options?.originBootstrapLoading ?? null,
35
- originBlocked: store.options?.originBlocked ?? null,
36
- originBlockReason: store.options?.originBlockReason ?? null,
37
- hasToken: store.options?.hasToken ?? null,
38
- backendUrl: store.credentials?.backendUrl ? '(set)' : '(empty)',
39
- token: store.credentials?.token ? `(len ${store.credentials.token.length})` : '(empty)',
40
- serverOriginsResolved: store.options?.serverOriginsResolved ?? null,
41
- }
42
- }
43
-
44
- export function describeApp(app) {
45
- if (!app) return { app: null }
46
- return { appUid: app._uid ?? null }
47
- }