@kennofizet/apphub-frontend 0.1.1 → 0.1.3

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.1",
3
+ "version": "0.1.3",
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",
@@ -15,6 +15,12 @@ export function isBackendReadyForApp(app) {
15
15
  return !!(store?.credentials?.backendUrl && store?.credentials?.token)
16
16
  }
17
17
 
18
+ export function isHostApiReadyForApp(app) {
19
+ const store = getAppHubStore(app)
20
+ if (!store?.credentials?.backendUrl || !store?.credentials?.token) return false
21
+ return store.facade?.hasImpl?.() === true
22
+ }
23
+
18
24
  /**
19
25
  * Host-only API — use in Hub shell components. Not provided via inject
20
26
  * so publisher app code cannot access grantBridgeScope or internal docs.
package/src/index.js CHANGED
@@ -136,6 +136,9 @@ 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
+ }
139
142
  }
140
143
 
141
144
  function applyCachedBootstrapIfAny(store) {
@@ -156,9 +159,12 @@ async function fetchBootstrapSession(store) {
156
159
  const res = await store.facade.bootstrap()
157
160
  if (res) applyBootstrapOrigins(store, res)
158
161
  })().catch(() => {
162
+ const wasLoading = store.options.originBootstrapLoading === true
159
163
  store.options.originBootstrapLoading = false
160
164
  if (!store.options.serverOriginsResolved) {
161
165
  reconcileOriginSafety(store)
166
+ } else if (!store.options.originBlocked && (wasLoading || !store.facade?.hasImpl?.())) {
167
+ enableModuleApi(store)
162
168
  }
163
169
  }).finally(() => {
164
170
  bootstrapInflight.delete(store)
@@ -202,8 +208,6 @@ function reconcileOriginSafety(store) {
202
208
 
203
209
  if (store.options.originBlocked && !wasBlocked) {
204
210
  disableModuleServices(store)
205
- } else if (!store.options.originBlocked && (wasBlocked || wasLoading)) {
206
- enableModuleApi(store)
207
211
  }
208
212
  }
209
213
 
@@ -243,6 +247,9 @@ function createApiFacade() {
243
247
  setImpl(next) {
244
248
  impl = next
245
249
  },
250
+ hasImpl() {
251
+ return impl != null
252
+ },
246
253
  bootstrap: (...args) => impl?.bootstrap?.(...args),
247
254
  apps: (...args) => impl?.apps?.(...args),
248
255
  launch: (...args) => impl?.launch?.(...args),
@@ -284,13 +291,14 @@ function applyModuleOptions(store, options = {}) {
284
291
  hostedSandboxSameOrigin: nextPublic.hostedSandboxSameOrigin,
285
292
  enforceDevFriendlyOrigins: nextPublic.enforceDevFriendlyOrigins,
286
293
  })
294
+ Object.assign(store.credentials, buildCredentials(options))
295
+ store._zonesSyncKey = null
287
296
  applyOriginSafety(store.options, options)
288
297
  if (store.credentials.backendUrl && store.credentials.token) {
289
298
  void startBootstrapSession(store)
290
299
  } else {
291
300
  reconcileOriginSafety(store)
292
301
  }
293
- Object.assign(store.credentials, buildCredentials(options))
294
302
  }
295
303
 
296
304
  function syncApi(store) {
@@ -323,11 +331,29 @@ function ensureZoneContext(app, store) {
323
331
  app.provide(APPHUB_ZONE_CONTEXT_KEY, store.zoneContext)
324
332
  }
325
333
 
326
- function syncZoneContext(store) {
334
+ function zoneSyncKey(store) {
335
+ const { coreUrl, token } = store.credentials
336
+ return `${coreUrl}::${token}`
337
+ }
338
+
339
+ function syncZoneContext(store, { force = false } = {}) {
327
340
  syncCoreApi(store)
328
- if (store.zoneContext) {
329
- store.zoneContext.refresh({ skipBootstrap: true })
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
+ }
347
+ if (store._zonesRefreshInflight) {
348
+ return store._zonesRefreshInflight
330
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
331
357
  }
332
358
 
333
359
  function ensureModuleState(app, store) {
@@ -100,7 +100,7 @@
100
100
  import { computed, getCurrentInstance, inject, onMounted, ref, watch } from 'vue'
101
101
  import {
102
102
  getHostApiForApp,
103
- isBackendReadyForApp,
103
+ isHostApiReadyForApp,
104
104
  resolveRootApp,
105
105
  } from '../../../composables/useAppHubHostApi.js'
106
106
  import { useAppHubZoneContext } from '../../../composables/useAppHubZoneContext.js'
@@ -152,11 +152,19 @@ const labels = computed(() => ({
152
152
 
153
153
  function hostApiOptions() {
154
154
  return {
155
- backendReady: isBackendReadyForApp(rootApp),
155
+ backendReady: isHostApiReadyForApp(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
+
160
168
  async function reloadCatalog() {
161
169
  if (!rootApp) return
162
170
  await appStore.loadCatalog(getHostApiForApp(rootApp), hostApiOptions())
@@ -197,14 +205,26 @@ onMounted(() => {
197
205
  watch(
198
206
  () => moduleOptions?.hasToken,
199
207
  (hasToken) => {
200
- if (hasToken && !catalog.loaded) reloadCatalog()
208
+ if (hasToken && canLoadCatalog() && !catalog.loaded) reloadCatalog()
209
+ },
210
+ )
211
+
212
+ watch(
213
+ () => moduleOptions?.originBootstrapLoading,
214
+ (loading, prevLoading) => {
215
+ if (prevLoading && !loading && canLoadCatalog()) {
216
+ reloadCatalog()
217
+ }
201
218
  },
202
219
  )
203
220
 
204
221
  watch(
205
222
  () => [zone?.state?.selectedZoneId, zone?.state?.viewAllZones],
206
- () => {
207
- if (moduleOptions?.hasToken) reloadCatalog()
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()
208
228
  },
209
229
  )
210
230
  </script>
@@ -65,7 +65,7 @@
65
65
  import { computed, getCurrentInstance, inject, onMounted, reactive, ref, watch } from 'vue'
66
66
  import {
67
67
  getHostApiForApp,
68
- isBackendReadyForApp,
68
+ isHostApiReadyForApp,
69
69
  resolveRootApp,
70
70
  } from '../../../composables/useAppHubHostApi.js'
71
71
  import { useAppHubZoneContext } from '../../../composables/useAppHubZoneContext.js'
@@ -119,11 +119,19 @@ const labels = computed(() => ({
119
119
 
120
120
  function hostApiOptions() {
121
121
  return {
122
- backendReady: isBackendReadyForApp(rootApp),
122
+ backendReady: isHostApiReadyForApp(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
+
127
135
  async function reloadCatalog() {
128
136
  if (!rootApp) return
129
137
  await appStore.loadCatalog(getHostApiForApp(rootApp), hostApiOptions())
@@ -171,14 +179,26 @@ onMounted(() => {
171
179
  watch(
172
180
  () => moduleOptions?.hasToken,
173
181
  (hasToken) => {
174
- if (hasToken && !catalog.loaded) reloadCatalog()
182
+ if (hasToken && canLoadCatalog() && !catalog.loaded) reloadCatalog()
183
+ },
184
+ )
185
+
186
+ watch(
187
+ () => moduleOptions?.originBootstrapLoading,
188
+ (loading, prevLoading) => {
189
+ if (prevLoading && !loading && canLoadCatalog()) {
190
+ reloadCatalog()
191
+ }
175
192
  },
176
193
  )
177
194
 
178
195
  watch(
179
196
  () => [zone?.state?.selectedZoneId, zone?.state?.viewAllZones],
180
- () => {
181
- if (moduleOptions?.hasToken) reloadCatalog()
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()
182
202
  },
183
203
  )
184
204
  </script>
@@ -102,7 +102,7 @@ export function createAppStoreState(options = {}) {
102
102
  const append = options.append === true
103
103
  const backendReady = options.backendReady !== false
104
104
 
105
- if (!backendReady || !hostApi?.apps) {
105
+ if (!backendReady || !hostApi?.hasImpl?.()) {
106
106
  if (!append) {
107
107
  bucket.items = []
108
108
  bucket.error = 'no_api'