@kennofizet/apphub-frontend 0.1.6 → 0.1.8
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 +1 -1
- package/src/composables/useAppHubHostApi.js +14 -3
- package/src/index.js +75 -12
- package/src/moduleStore.js +15 -2
- package/src/modules/app-store/components/AppHubAppStoreApp.vue +35 -15
- package/src/modules/app-store/components/AppHubDraftStoreApp.vue +11 -11
- package/src/modules/app-store/composables/useAppStore.js +29 -1
- package/src/utils/apphubDebug.js +47 -0
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { getCurrentInstance } from 'vue'
|
|
2
|
-
import { getAppHubStore } from '../moduleStore.js'
|
|
1
|
+
import { getCurrentInstance, inject } from 'vue'
|
|
2
|
+
import { APPHUB_MODULE_STORE_KEY, getAppHubStore } from '../moduleStore.js'
|
|
3
3
|
|
|
4
4
|
export function resolveRootApp(instance = getCurrentInstance()) {
|
|
5
5
|
if (!instance) return null
|
|
6
6
|
return instance.appContext?.app ?? instance.app ?? null
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/** Resolve module store — inject first (always same instance), then WeakMap by Vue app. */
|
|
10
|
+
export function useAppHubModuleStore() {
|
|
11
|
+
const fromInject = inject(APPHUB_MODULE_STORE_KEY, null)
|
|
12
|
+
if (fromInject) return fromInject
|
|
13
|
+
return getAppHubStore(resolveRootApp())
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export function getHostApiForApp(app) {
|
|
10
17
|
return getAppHubStore(app)?.facade ?? null
|
|
11
18
|
}
|
|
@@ -20,5 +27,9 @@ export function isBackendReadyForApp(app) {
|
|
|
20
27
|
* so publisher app code cannot access grantBridgeScope or internal docs.
|
|
21
28
|
*/
|
|
22
29
|
export function useAppHubHostApi() {
|
|
23
|
-
return
|
|
30
|
+
return useAppHubModuleStore()?.facade ?? null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isBackendReadyFromStore(store) {
|
|
34
|
+
return !!(store?.credentials?.backendUrl && store?.credentials?.token)
|
|
24
35
|
}
|
package/src/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { APPHUB_ZONE_CONTEXT_KEY } from './composables/useAppHubZoneContext.js'
|
|
|
8
8
|
import { createAppStoreState, provideAppStore } from './modules/app-store/index.js'
|
|
9
9
|
import { AppHubDesktop } from './modules/desktop/index.js'
|
|
10
10
|
import { AppHubRunner } from './modules/runner/index.js'
|
|
11
|
-
import { getAppHubStore, registerAppHubStore } from './moduleStore.js'
|
|
11
|
+
import { getAppHubStore, registerAppHubStore, APPHUB_MODULE_STORE_KEY } from './moduleStore.js'
|
|
12
12
|
import {
|
|
13
13
|
createWindowManagerState,
|
|
14
14
|
provideWindowManager,
|
|
@@ -24,6 +24,11 @@ import {
|
|
|
24
24
|
loadBootstrapCache,
|
|
25
25
|
saveBootstrapCache,
|
|
26
26
|
} from './utils/bootstrapCache.js'
|
|
27
|
+
import {
|
|
28
|
+
apphubDebug,
|
|
29
|
+
describeHostApi,
|
|
30
|
+
describeStore,
|
|
31
|
+
} from './utils/apphubDebug.js'
|
|
27
32
|
|
|
28
33
|
const bootstrapInflight = new WeakMap()
|
|
29
34
|
|
|
@@ -136,6 +141,7 @@ function applyBootstrapOrigins(store, bootstrapResponse, { fromCache = false } =
|
|
|
136
141
|
reconcileOriginSafety(store)
|
|
137
142
|
store.options.originBootstrapLoading = false
|
|
138
143
|
applyOriginSafety(store.options)
|
|
144
|
+
apphubDebug('bootstrap', 'applyBootstrapOrigins done', describeStore(store))
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
function applyCachedBootstrapIfAny(store) {
|
|
@@ -150,12 +156,24 @@ async function fetchBootstrapSession(store) {
|
|
|
150
156
|
if (promise) return promise
|
|
151
157
|
|
|
152
158
|
promise = (async () => {
|
|
159
|
+
apphubDebug('bootstrap', 'fetchBootstrapSession start', describeStore(store))
|
|
153
160
|
syncApi(store)
|
|
154
|
-
if (!store.facade?.bootstrap)
|
|
161
|
+
if (!store.facade?.bootstrap) {
|
|
162
|
+
apphubDebug('bootstrap', 'fetchBootstrapSession abort — no bootstrap fn', describeStore(store))
|
|
163
|
+
return
|
|
164
|
+
}
|
|
155
165
|
|
|
156
166
|
const res = await store.facade.bootstrap()
|
|
167
|
+
apphubDebug('bootstrap', 'fetchBootstrapSession bootstrap response', {
|
|
168
|
+
...describeStore(store),
|
|
169
|
+
hasResponse: !!res,
|
|
170
|
+
})
|
|
157
171
|
if (res) applyBootstrapOrigins(store, res)
|
|
158
|
-
})().catch(() => {
|
|
172
|
+
})().catch((err) => {
|
|
173
|
+
apphubDebug('bootstrap', 'fetchBootstrapSession failed', {
|
|
174
|
+
...describeStore(store),
|
|
175
|
+
error: err?.message ?? String(err),
|
|
176
|
+
})
|
|
159
177
|
store.options.originBootstrapLoading = false
|
|
160
178
|
if (!store.options.serverOriginsResolved) {
|
|
161
179
|
reconcileOriginSafety(store)
|
|
@@ -170,6 +188,11 @@ async function fetchBootstrapSession(store) {
|
|
|
170
188
|
|
|
171
189
|
function startBootstrapSession(store) {
|
|
172
190
|
const { backendUrl, token } = store.credentials
|
|
191
|
+
apphubDebug('bootstrap', 'startBootstrapSession', {
|
|
192
|
+
...describeStore(store),
|
|
193
|
+
hasBackendUrl: !!backendUrl,
|
|
194
|
+
hasToken: !!token,
|
|
195
|
+
})
|
|
173
196
|
if (!backendUrl || !token) {
|
|
174
197
|
store.options.originBootstrapLoading = false
|
|
175
198
|
reconcileOriginSafety(store)
|
|
@@ -180,11 +203,15 @@ function startBootstrapSession(store) {
|
|
|
180
203
|
applyOriginSafety(store.options)
|
|
181
204
|
|
|
182
205
|
const hadCache = applyCachedBootstrapIfAny(store)
|
|
206
|
+
apphubDebug('bootstrap', 'startBootstrapSession after cache', {
|
|
207
|
+
hadCache,
|
|
208
|
+
...describeStore(store),
|
|
209
|
+
})
|
|
183
210
|
if (!hadCache && !store.options.originBlocked) {
|
|
184
211
|
store.options.originBootstrapLoading = true
|
|
185
212
|
store.options.originBlocked = false
|
|
186
213
|
applyOriginSafety(store.options)
|
|
187
|
-
disableModuleServices(store)
|
|
214
|
+
disableModuleServices(store, 'startBootstrapSession:no-cache')
|
|
188
215
|
}
|
|
189
216
|
|
|
190
217
|
return fetchBootstrapSession(store)
|
|
@@ -195,21 +222,30 @@ function reconcileOriginSafety(store) {
|
|
|
195
222
|
const wasLoading = store.options.originBootstrapLoading === true
|
|
196
223
|
applyOriginSafety(store.options)
|
|
197
224
|
|
|
225
|
+
let action = 'noop'
|
|
198
226
|
if (store.options.originBootstrapLoading) {
|
|
199
|
-
disableModuleServices(store)
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
disableModuleServices(store)
|
|
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'
|
|
205
232
|
} else if (!store.options.originBlocked && (wasBlocked || wasLoading)) {
|
|
206
233
|
enableModuleApi(store)
|
|
234
|
+
action = 'enable'
|
|
207
235
|
}
|
|
236
|
+
|
|
237
|
+
apphubDebug('origin', 'reconcileOriginSafety', {
|
|
238
|
+
wasLoading,
|
|
239
|
+
wasBlocked,
|
|
240
|
+
action,
|
|
241
|
+
...describeStore(store),
|
|
242
|
+
})
|
|
208
243
|
}
|
|
209
244
|
|
|
210
|
-
function disableModuleServices(store) {
|
|
245
|
+
function disableModuleServices(store, reason = 'unknown') {
|
|
211
246
|
store.facade.setImpl(null)
|
|
212
247
|
store.coreApi = null
|
|
248
|
+
apphubDebug('api', 'disableModuleServices', { reason, ...describeStore(store) })
|
|
213
249
|
}
|
|
214
250
|
|
|
215
251
|
/** Window manager + app store — required even when origin is blocked (AppHubDesktop setup). */
|
|
@@ -221,6 +257,7 @@ function ensureModuleInfrastructure(vueApp, store) {
|
|
|
221
257
|
function enableModuleApi(store) {
|
|
222
258
|
syncApi(store)
|
|
223
259
|
syncZoneContext(store)
|
|
260
|
+
apphubDebug('api', 'enableModuleApi', describeStore(store))
|
|
224
261
|
}
|
|
225
262
|
|
|
226
263
|
function enableModuleServices(vueApp, store) {
|
|
@@ -300,6 +337,14 @@ function applyModuleOptions(store, options = {}) {
|
|
|
300
337
|
enforceDevFriendlyOrigins: nextPublic.enforceDevFriendlyOrigins,
|
|
301
338
|
})
|
|
302
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
|
+
})
|
|
303
348
|
if (store.credentials.backendUrl && store.credentials.token) {
|
|
304
349
|
if (credsUnchanged) {
|
|
305
350
|
reconcileOriginSafety(store)
|
|
@@ -320,6 +365,10 @@ function syncApi(store) {
|
|
|
320
365
|
})
|
|
321
366
|
: null
|
|
322
367
|
facade.setImpl(api)
|
|
368
|
+
apphubDebug('api', 'syncApi', {
|
|
369
|
+
implSet: api != null,
|
|
370
|
+
...describeStore(store),
|
|
371
|
+
})
|
|
323
372
|
}
|
|
324
373
|
|
|
325
374
|
function syncCoreApi(store) {
|
|
@@ -368,12 +417,25 @@ export function installAppHubModule(vueApp, options = {}) {
|
|
|
368
417
|
|
|
369
418
|
if (store) {
|
|
370
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
|
+
})
|
|
371
427
|
applyModuleOptions(store, options)
|
|
372
428
|
ensureModuleInfrastructure(vueApp, store)
|
|
373
429
|
reconcileOriginSafety(store)
|
|
374
430
|
return store.facade
|
|
375
431
|
}
|
|
376
432
|
|
|
433
|
+
apphubDebug('install', 'installAppHubModule first install', {
|
|
434
|
+
appUid: vueApp._uid ?? null,
|
|
435
|
+
hasToken: !!(options.token),
|
|
436
|
+
backendUrl: options.backendUrl ? '(set)' : '(empty)',
|
|
437
|
+
})
|
|
438
|
+
|
|
377
439
|
const facade = createApiFacade()
|
|
378
440
|
const moduleOptions = reactive(buildPublicOptions(options))
|
|
379
441
|
applyOriginSafety(moduleOptions, options)
|
|
@@ -393,6 +455,7 @@ export function installAppHubModule(vueApp, options = {}) {
|
|
|
393
455
|
|
|
394
456
|
vueApp.provide('apphubOptions', moduleOptions)
|
|
395
457
|
vueApp.provide('apphubHostApp', vueApp)
|
|
458
|
+
vueApp.provide(APPHUB_MODULE_STORE_KEY, store)
|
|
396
459
|
vueApp.component('AppHubDesktop', AppHubDesktop)
|
|
397
460
|
vueApp.component('AppHubRunner', AppHubRunner)
|
|
398
461
|
|
|
@@ -407,7 +470,7 @@ export function isAppHubModuleInstalled(vueApp) {
|
|
|
407
470
|
return vueApp != null && getAppHubStore(vueApp) != null
|
|
408
471
|
}
|
|
409
472
|
|
|
410
|
-
export { useAppHubHostApi } from './composables/useAppHubHostApi.js'
|
|
473
|
+
export { useAppHubHostApi, useAppHubModuleStore } from './composables/useAppHubHostApi.js'
|
|
411
474
|
export { useAppHubZoneContext } from './composables/useAppHubZoneContext.js'
|
|
412
475
|
export { createAppHubApi } from './api/index.js'
|
|
413
476
|
export { createCoreApi } from './api/coreApi.js'
|
package/src/moduleStore.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
const
|
|
1
|
+
/** Internal inject key — package components only; not part of public host API. */
|
|
2
|
+
export const APPHUB_MODULE_STORE_KEY = Symbol('apphubModuleStore')
|
|
3
|
+
|
|
4
|
+
const REGISTRY_KEY = '__kennofizet_apphub_installed_apps__'
|
|
5
|
+
|
|
6
|
+
/** Shared WeakMap — Vite may load this module twice (host bundle vs SFC chunks). */
|
|
7
|
+
function getInstalledAppsRegistry() {
|
|
8
|
+
const root = typeof globalThis !== 'undefined' ? globalThis : global
|
|
9
|
+
if (!root[REGISTRY_KEY]) {
|
|
10
|
+
root[REGISTRY_KEY] = new WeakMap()
|
|
11
|
+
}
|
|
12
|
+
return root[REGISTRY_KEY]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const installedApps = getInstalledAppsRegistry()
|
|
3
16
|
|
|
4
17
|
export function registerAppHubStore(app, store) {
|
|
5
18
|
installedApps.set(app, store)
|
|
@@ -97,11 +97,11 @@
|
|
|
97
97
|
</template>
|
|
98
98
|
|
|
99
99
|
<script setup>
|
|
100
|
-
import { computed,
|
|
100
|
+
import { computed, inject, onMounted, ref, watch } from 'vue'
|
|
101
101
|
import {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
isBackendReadyFromStore,
|
|
103
|
+
useAppHubHostApi,
|
|
104
|
+
useAppHubModuleStore,
|
|
105
105
|
} from '../../../composables/useAppHubHostApi.js'
|
|
106
106
|
import { useAppHubZoneContext } from '../../../composables/useAppHubZoneContext.js'
|
|
107
107
|
import { t } from '../../../i18n/index.js'
|
|
@@ -109,6 +109,7 @@ 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'
|
|
112
113
|
import AppHubAppStoreCard from './AppHubAppStoreCard.vue'
|
|
113
114
|
import AppHubAppStoreSettingsPanel from './AppHubAppStoreSettingsPanel.vue'
|
|
114
115
|
|
|
@@ -122,7 +123,9 @@ const props = defineProps({
|
|
|
122
123
|
const settingsOpen = ref(false)
|
|
123
124
|
const appStore = useAppStore()
|
|
124
125
|
const catalog = appStore.catalogs.store
|
|
125
|
-
const
|
|
126
|
+
const hubStore = useAppHubModuleStore()
|
|
127
|
+
const hostApi = useAppHubHostApi()
|
|
128
|
+
const rootApp = inject('apphubHostApp', null)
|
|
126
129
|
const zone = useAppHubZoneContext()
|
|
127
130
|
const moduleOptions = inject('apphubOptions', {})
|
|
128
131
|
const lang = computed(() => resolveLang(moduleOptions?.language, 'vi'))
|
|
@@ -152,19 +155,29 @@ const labels = computed(() => ({
|
|
|
152
155
|
|
|
153
156
|
function hostApiOptions() {
|
|
154
157
|
return {
|
|
155
|
-
backendReady:
|
|
158
|
+
backendReady: isBackendReadyFromStore(hubStore),
|
|
156
159
|
mode: CATALOG_MODE_STORE,
|
|
157
160
|
}
|
|
158
161
|
}
|
|
159
162
|
|
|
160
|
-
async function reloadCatalog() {
|
|
161
|
-
|
|
162
|
-
|
|
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)
|
|
163
177
|
}
|
|
164
178
|
|
|
165
179
|
async function loadMore() {
|
|
166
|
-
|
|
167
|
-
await appStore.loadMoreCatalog(getHostApiForApp(rootApp), CATALOG_MODE_STORE, hostApiOptions())
|
|
180
|
+
await appStore.loadMoreCatalog(hostApi, CATALOG_MODE_STORE, hostApiOptions())
|
|
168
181
|
}
|
|
169
182
|
|
|
170
183
|
const { rootRef: scrollRoot, sentinelRef: scrollSentinel } = useCatalogInfiniteScroll({
|
|
@@ -191,21 +204,28 @@ async function onUninstall(app) {
|
|
|
191
204
|
}
|
|
192
205
|
|
|
193
206
|
onMounted(() => {
|
|
194
|
-
if (!catalog.loaded) reloadCatalog()
|
|
207
|
+
if (!catalog.loaded) reloadCatalog('onMounted')
|
|
195
208
|
})
|
|
196
209
|
|
|
197
210
|
watch(
|
|
198
211
|
() => moduleOptions?.hasToken,
|
|
199
212
|
(hasToken) => {
|
|
200
|
-
if (hasToken && !catalog.loaded) reloadCatalog()
|
|
213
|
+
if (hasToken && !catalog.loaded) reloadCatalog('watch:hasToken')
|
|
201
214
|
},
|
|
202
215
|
)
|
|
203
216
|
|
|
204
217
|
watch(
|
|
205
218
|
() => moduleOptions?.originBootstrapLoading,
|
|
206
219
|
(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
|
+
})
|
|
207
227
|
if (wasLoading && !loading && !moduleOptions?.originBlocked) {
|
|
208
|
-
if (!catalog.loaded || catalog.error === 'no_api') reloadCatalog()
|
|
228
|
+
if (!catalog.loaded || catalog.error === 'no_api') reloadCatalog('watch:bootstrap-done')
|
|
209
229
|
}
|
|
210
230
|
},
|
|
211
231
|
)
|
|
@@ -213,7 +233,7 @@ watch(
|
|
|
213
233
|
watch(
|
|
214
234
|
() => [zone?.state?.selectedZoneId, zone?.state?.viewAllZones],
|
|
215
235
|
() => {
|
|
216
|
-
if (moduleOptions?.hasToken) reloadCatalog()
|
|
236
|
+
if (moduleOptions?.hasToken) reloadCatalog('watch:zone')
|
|
217
237
|
},
|
|
218
238
|
)
|
|
219
239
|
</script>
|
|
@@ -62,11 +62,11 @@
|
|
|
62
62
|
</template>
|
|
63
63
|
|
|
64
64
|
<script setup>
|
|
65
|
-
import { computed,
|
|
65
|
+
import { computed, inject, onMounted, reactive, ref, watch } from 'vue'
|
|
66
66
|
import {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
isBackendReadyFromStore,
|
|
68
|
+
useAppHubHostApi,
|
|
69
|
+
useAppHubModuleStore,
|
|
70
70
|
} from '../../../composables/useAppHubHostApi.js'
|
|
71
71
|
import { useAppHubZoneContext } from '../../../composables/useAppHubZoneContext.js'
|
|
72
72
|
import { t } from '../../../i18n/index.js'
|
|
@@ -83,7 +83,9 @@ const props = defineProps({
|
|
|
83
83
|
|
|
84
84
|
const appStore = useAppStore()
|
|
85
85
|
const catalog = appStore.catalogs.draft
|
|
86
|
-
const
|
|
86
|
+
const hubStore = useAppHubModuleStore()
|
|
87
|
+
const hostApi = useAppHubHostApi()
|
|
88
|
+
const rootApp = inject('apphubHostApp', null)
|
|
87
89
|
const zone = useAppHubZoneContext()
|
|
88
90
|
const moduleOptions = inject('apphubOptions', {})
|
|
89
91
|
const lang = computed(() => resolveLang(moduleOptions?.language, 'vi'))
|
|
@@ -119,19 +121,17 @@ const labels = computed(() => ({
|
|
|
119
121
|
|
|
120
122
|
function hostApiOptions() {
|
|
121
123
|
return {
|
|
122
|
-
backendReady:
|
|
124
|
+
backendReady: isBackendReadyFromStore(hubStore),
|
|
123
125
|
mode: CATALOG_MODE_DRAFT,
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
async function reloadCatalog() {
|
|
128
|
-
|
|
129
|
-
await appStore.loadCatalog(getHostApiForApp(rootApp), hostApiOptions())
|
|
130
|
+
await appStore.loadCatalog(hostApi, hostApiOptions())
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
async function loadMore() {
|
|
133
|
-
|
|
134
|
-
await appStore.loadMoreCatalog(getHostApiForApp(rootApp), CATALOG_MODE_DRAFT, hostApiOptions())
|
|
134
|
+
await appStore.loadMoreCatalog(hostApi, CATALOG_MODE_DRAFT, hostApiOptions())
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
const { rootRef: scrollRoot, sentinelRef: scrollSentinel } = useCatalogInfiniteScroll({
|
|
@@ -150,7 +150,7 @@ async function onUninstall(app) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
async function onPing(app) {
|
|
153
|
-
const api =
|
|
153
|
+
const api = hostApi
|
|
154
154
|
if (!api?.ping || !app?.slug) return
|
|
155
155
|
pingingSlug.value = app.slug
|
|
156
156
|
delete pingResults[app.slug]
|
|
@@ -1,6 +1,7 @@
|
|
|
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'
|
|
4
5
|
|
|
5
6
|
const APP_STORE_KEY = 'apphubAppStore'
|
|
6
7
|
|
|
@@ -28,6 +29,12 @@ function filterItems(items, search) {
|
|
|
28
29
|
)
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
function hostApiReady(hostApi) {
|
|
33
|
+
if (!hostApi?.apps) return false
|
|
34
|
+
if (typeof hostApi.hasImpl === 'function') return hostApi.hasImpl()
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
/**
|
|
32
39
|
* Independent App Store module — separate catalog buckets per mode (store / draft).
|
|
33
40
|
*/
|
|
@@ -102,7 +109,20 @@ export function createAppStoreState(options = {}) {
|
|
|
102
109
|
const append = options.append === true
|
|
103
110
|
const backendReady = options.backendReady !== false
|
|
104
111
|
|
|
105
|
-
|
|
112
|
+
apphubDebug('catalog', 'loadCatalog called', {
|
|
113
|
+
mode,
|
|
114
|
+
append,
|
|
115
|
+
backendReady,
|
|
116
|
+
...describeHostApi(hostApi),
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
if (!backendReady || !hostApiReady(hostApi)) {
|
|
120
|
+
apphubDebug('catalog', 'loadCatalog → no_api (not ready)', {
|
|
121
|
+
mode,
|
|
122
|
+
append,
|
|
123
|
+
backendReady,
|
|
124
|
+
...describeHostApi(hostApi),
|
|
125
|
+
})
|
|
106
126
|
if (!append) {
|
|
107
127
|
bucket.items = []
|
|
108
128
|
bucket.error = 'no_api'
|
|
@@ -131,8 +151,16 @@ export function createAppStoreState(options = {}) {
|
|
|
131
151
|
}
|
|
132
152
|
|
|
133
153
|
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
|
+
})
|
|
134
161
|
if (res === undefined || res === null) {
|
|
135
162
|
if (!append) {
|
|
163
|
+
apphubDebug('catalog', 'loadCatalog → no_api (undefined response)', { mode })
|
|
136
164
|
bucket.items = []
|
|
137
165
|
bucket.error = 'no_api'
|
|
138
166
|
bucket.loaded = false
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
}
|