@kennofizet/apphub-frontend 0.1.4 → 0.1.6

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.4",
3
+ "version": "0.1.6",
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",
@@ -6,36 +6,15 @@ export function resolveRootApp(instance = getCurrentInstance()) {
6
6
  return instance.appContext?.app ?? instance.app ?? null
7
7
  }
8
8
 
9
- export function resolveHostApp(app) {
10
- if (app != null) return app
11
- const instance = getCurrentInstance()
12
- if (!instance) return null
13
- return instance.appContext?.app ?? instance.app ?? null
14
- }
15
-
16
- export function getAppHubStoreForApp(app) {
17
- const resolved = resolveHostApp(app)
18
- return resolved != null ? getAppHubStore(resolved) : null
19
- }
20
-
21
9
  export function getHostApiForApp(app) {
22
- return getAppHubStoreForApp(app)?.facade ?? null
10
+ return getAppHubStore(app)?.facade ?? null
23
11
  }
24
12
 
25
13
  export function isBackendReadyForApp(app) {
26
- const store = getAppHubStoreForApp(app)
14
+ const store = getAppHubStore(app)
27
15
  return !!(store?.credentials?.backendUrl && store?.credentials?.token)
28
16
  }
29
17
 
30
- export function isHostApiReadyForApp(app) {
31
- const store = getAppHubStoreForApp(app)
32
- if (!store?.credentials?.backendUrl || !store?.credentials?.token) return false
33
- if (typeof store.facade?.hasImpl === 'function') {
34
- return store.facade.hasImpl()
35
- }
36
- return !!store.facade
37
- }
38
-
39
18
  /**
40
19
  * Host-only API — use in Hub shell components. Not provided via inject
41
20
  * so publisher app code cannot access grantBridgeScope or internal docs.
package/src/index.js CHANGED
@@ -136,9 +136,6 @@ function applyBootstrapOrigins(store, bootstrapResponse, { fromCache = false } =
136
136
  reconcileOriginSafety(store)
137
137
  store.options.originBootstrapLoading = false
138
138
  applyOriginSafety(store.options)
139
- if (!store.options.originBlocked) {
140
- enableModuleApi(store)
141
- }
142
139
  }
143
140
 
144
141
  function applyCachedBootstrapIfAny(store) {
@@ -159,12 +156,9 @@ async function fetchBootstrapSession(store) {
159
156
  const res = await store.facade.bootstrap()
160
157
  if (res) applyBootstrapOrigins(store, res)
161
158
  })().catch(() => {
162
- const wasLoading = store.options.originBootstrapLoading === true
163
159
  store.options.originBootstrapLoading = false
164
160
  if (!store.options.serverOriginsResolved) {
165
161
  reconcileOriginSafety(store)
166
- } else if (!store.options.originBlocked && (wasLoading || !store.facade?.hasImpl?.())) {
167
- enableModuleApi(store)
168
162
  }
169
163
  }).finally(() => {
170
164
  bootstrapInflight.delete(store)
@@ -208,6 +202,8 @@ function reconcileOriginSafety(store) {
208
202
 
209
203
  if (store.options.originBlocked && !wasBlocked) {
210
204
  disableModuleServices(store)
205
+ } else if (!store.options.originBlocked && (wasBlocked || wasLoading)) {
206
+ enableModuleApi(store)
211
207
  }
212
208
  }
213
209
 
@@ -270,7 +266,19 @@ function createApiFacade() {
270
266
  }
271
267
  }
272
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
+
273
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
+
274
282
  const nextPublic = buildPublicOptions({ ...options, token: options.token ?? store.credentials.token })
275
283
  Object.assign(store.options, {
276
284
  language: nextPublic.language,
@@ -291,11 +299,13 @@ function applyModuleOptions(store, options = {}) {
291
299
  hostedSandboxSameOrigin: nextPublic.hostedSandboxSameOrigin,
292
300
  enforceDevFriendlyOrigins: nextPublic.enforceDevFriendlyOrigins,
293
301
  })
294
- Object.assign(store.credentials, buildCredentials(options))
295
- store._zonesSyncKey = null
296
302
  applyOriginSafety(store.options, options)
297
303
  if (store.credentials.backendUrl && store.credentials.token) {
298
- void startBootstrapSession(store)
304
+ if (credsUnchanged) {
305
+ reconcileOriginSafety(store)
306
+ } else {
307
+ void startBootstrapSession(store)
308
+ }
299
309
  } else {
300
310
  reconcileOriginSafety(store)
301
311
  }
@@ -331,29 +341,11 @@ function ensureZoneContext(app, store) {
331
341
  app.provide(APPHUB_ZONE_CONTEXT_KEY, store.zoneContext)
332
342
  }
333
343
 
334
- function zoneSyncKey(store) {
335
- const { coreUrl, token } = store.credentials
336
- return `${coreUrl}::${token}`
337
- }
338
-
339
- function syncZoneContext(store, { force = false } = {}) {
344
+ function syncZoneContext(store) {
340
345
  syncCoreApi(store)
341
- if (!store.zoneContext || !store.coreApi) return
342
-
343
- const key = zoneSyncKey(store)
344
- if (!force && store._zonesSyncKey === key && store.zoneContext.state.zones.length > 0) {
345
- return store._zonesRefreshInflight ?? undefined
346
+ if (store.zoneContext) {
347
+ store.zoneContext.refresh({ skipBootstrap: true })
346
348
  }
347
- if (store._zonesRefreshInflight) {
348
- return store._zonesRefreshInflight
349
- }
350
-
351
- store._zonesSyncKey = key
352
- store._zonesRefreshInflight = store.zoneContext.refresh({ skipBootstrap: true })
353
- .finally(() => {
354
- store._zonesRefreshInflight = null
355
- })
356
- return store._zonesRefreshInflight
357
349
  }
358
350
 
359
351
  function ensureModuleState(app, store) {
@@ -378,13 +370,7 @@ export function installAppHubModule(vueApp, options = {}) {
378
370
  vueApp.provide('apphubHostApp', vueApp)
379
371
  applyModuleOptions(store, options)
380
372
  ensureModuleInfrastructure(vueApp, store)
381
- // While bootstrap is in flight, leave API state to startBootstrapSession — do not
382
- // disable again (hub-host-starter may re-apply config when parent re-sends postMessage).
383
- if (store.options.originBlocked) {
384
- disableModuleServices(store)
385
- } else if (!store.options.originBootstrapLoading) {
386
- enableModuleApi(store)
387
- }
373
+ reconcileOriginSafety(store)
388
374
  return store.facade
389
375
  }
390
376
 
@@ -405,8 +391,8 @@ export function installAppHubModule(vueApp, options = {}) {
405
391
  }
406
392
  registerAppHubStore(vueApp, store)
407
393
 
408
- vueApp.provide('apphubHostApp', vueApp)
409
394
  vueApp.provide('apphubOptions', moduleOptions)
395
+ vueApp.provide('apphubHostApp', vueApp)
410
396
  vueApp.component('AppHubDesktop', AppHubDesktop)
411
397
  vueApp.component('AppHubRunner', AppHubRunner)
412
398
 
@@ -100,7 +100,7 @@
100
100
  import { computed, getCurrentInstance, inject, onMounted, ref, watch } from 'vue'
101
101
  import {
102
102
  getHostApiForApp,
103
- isHostApiReadyForApp,
103
+ isBackendReadyForApp,
104
104
  resolveRootApp,
105
105
  } from '../../../composables/useAppHubHostApi.js'
106
106
  import { useAppHubZoneContext } from '../../../composables/useAppHubZoneContext.js'
@@ -152,19 +152,11 @@ const labels = computed(() => ({
152
152
 
153
153
  function hostApiOptions() {
154
154
  return {
155
- backendReady: isHostApiReadyForApp(rootApp),
155
+ backendReady: isBackendReadyForApp(rootApp),
156
156
  mode: CATALOG_MODE_STORE,
157
157
  }
158
158
  }
159
159
 
160
- function canLoadCatalog() {
161
- return (
162
- !moduleOptions?.originBootstrapLoading
163
- && !moduleOptions?.originBlocked
164
- && isHostApiReadyForApp(rootApp)
165
- )
166
- }
167
-
168
160
  async function reloadCatalog() {
169
161
  if (!rootApp) return
170
162
  await appStore.loadCatalog(getHostApiForApp(rootApp), hostApiOptions())
@@ -205,26 +197,23 @@ onMounted(() => {
205
197
  watch(
206
198
  () => moduleOptions?.hasToken,
207
199
  (hasToken) => {
208
- if (hasToken && canLoadCatalog() && !catalog.loaded) reloadCatalog()
200
+ if (hasToken && !catalog.loaded) reloadCatalog()
209
201
  },
210
202
  )
211
203
 
212
204
  watch(
213
205
  () => moduleOptions?.originBootstrapLoading,
214
- (loading, prevLoading) => {
215
- if (prevLoading && !loading && canLoadCatalog()) {
216
- reloadCatalog()
206
+ (loading, wasLoading) => {
207
+ if (wasLoading && !loading && !moduleOptions?.originBlocked) {
208
+ if (!catalog.loaded || catalog.error === 'no_api') reloadCatalog()
217
209
  }
218
210
  },
219
211
  )
220
212
 
221
213
  watch(
222
214
  () => [zone?.state?.selectedZoneId, zone?.state?.viewAllZones],
223
- (current, previous) => {
224
- if (!previous) return
225
- if (current[0] === previous[0] && current[1] === previous[1]) return
226
- if (!canLoadCatalog() || !catalog.loaded) return
227
- reloadCatalog()
215
+ () => {
216
+ if (moduleOptions?.hasToken) reloadCatalog()
228
217
  },
229
218
  )
230
219
  </script>
@@ -65,7 +65,7 @@
65
65
  import { computed, getCurrentInstance, inject, onMounted, reactive, ref, watch } from 'vue'
66
66
  import {
67
67
  getHostApiForApp,
68
- isHostApiReadyForApp,
68
+ isBackendReadyForApp,
69
69
  resolveRootApp,
70
70
  } from '../../../composables/useAppHubHostApi.js'
71
71
  import { useAppHubZoneContext } from '../../../composables/useAppHubZoneContext.js'
@@ -119,19 +119,11 @@ const labels = computed(() => ({
119
119
 
120
120
  function hostApiOptions() {
121
121
  return {
122
- backendReady: isHostApiReadyForApp(rootApp),
122
+ backendReady: isBackendReadyForApp(rootApp),
123
123
  mode: CATALOG_MODE_DRAFT,
124
124
  }
125
125
  }
126
126
 
127
- function canLoadCatalog() {
128
- return (
129
- !moduleOptions?.originBootstrapLoading
130
- && !moduleOptions?.originBlocked
131
- && isHostApiReadyForApp(rootApp)
132
- )
133
- }
134
-
135
127
  async function reloadCatalog() {
136
128
  if (!rootApp) return
137
129
  await appStore.loadCatalog(getHostApiForApp(rootApp), hostApiOptions())
@@ -179,26 +171,23 @@ onMounted(() => {
179
171
  watch(
180
172
  () => moduleOptions?.hasToken,
181
173
  (hasToken) => {
182
- if (hasToken && canLoadCatalog() && !catalog.loaded) reloadCatalog()
174
+ if (hasToken && !catalog.loaded) reloadCatalog()
183
175
  },
184
176
  )
185
177
 
186
178
  watch(
187
179
  () => moduleOptions?.originBootstrapLoading,
188
- (loading, prevLoading) => {
189
- if (prevLoading && !loading && canLoadCatalog()) {
190
- reloadCatalog()
180
+ (loading, wasLoading) => {
181
+ if (wasLoading && !loading && !moduleOptions?.originBlocked) {
182
+ if (!catalog.loaded || catalog.error === 'no_api') reloadCatalog()
191
183
  }
192
184
  },
193
185
  )
194
186
 
195
187
  watch(
196
188
  () => [zone?.state?.selectedZoneId, zone?.state?.viewAllZones],
197
- (current, previous) => {
198
- if (!previous) return
199
- if (current[0] === previous[0] && current[1] === previous[1]) return
200
- if (!canLoadCatalog() || !catalog.loaded) return
201
- reloadCatalog()
189
+ () => {
190
+ if (moduleOptions?.hasToken) reloadCatalog()
202
191
  },
203
192
  )
204
193
  </script>
@@ -102,10 +102,7 @@ export function createAppStoreState(options = {}) {
102
102
  const append = options.append === true
103
103
  const backendReady = options.backendReady !== false
104
104
 
105
- const facadeReady = hostApi && (
106
- typeof hostApi.hasImpl === 'function' ? hostApi.hasImpl() : true
107
- )
108
- if (!backendReady || !facadeReady) {
105
+ if (!backendReady || !hostApi?.apps) {
109
106
  if (!append) {
110
107
  bucket.items = []
111
108
  bucket.error = 'no_api'