@ossy/app 3.11.1 → 3.13.0
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/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/app",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"source": "./src/index.js",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -53,17 +53,18 @@
|
|
|
53
53
|
"@ossy/design-system": "^3.11.0",
|
|
54
54
|
"@ossy/locale": "^3.4.0",
|
|
55
55
|
"@ossy/manifest": "^3.9.0",
|
|
56
|
-
"@ossy/package-catalog": "^3.
|
|
56
|
+
"@ossy/package-catalog": "^3.12.0",
|
|
57
57
|
"@ossy/pages": "^3.0.9",
|
|
58
|
-
"@ossy/platform": "^3.
|
|
59
|
-
"@ossy/resources": "^3.
|
|
58
|
+
"@ossy/platform": "^3.13.0",
|
|
59
|
+
"@ossy/resources": "^3.13.0",
|
|
60
60
|
"@ossy/router": "^3.0.9",
|
|
61
61
|
"@ossy/router-react": "^3.11.0",
|
|
62
62
|
"@ossy/schema": "^3.8.0",
|
|
63
|
-
"@ossy/sdk": "^3.
|
|
64
|
-
"@ossy/sdk-react": "^3.
|
|
63
|
+
"@ossy/sdk": "^3.12.0",
|
|
64
|
+
"@ossy/sdk-react": "^3.12.0",
|
|
65
65
|
"@ossy/themes": "^3.8.0",
|
|
66
|
-
"@ossy/
|
|
66
|
+
"@ossy/users": "^3.12.0",
|
|
67
|
+
"@ossy/workspaces": "^3.12.0",
|
|
67
68
|
"@rollup/plugin-alias": "^6.0.0",
|
|
68
69
|
"@rollup/plugin-babel": "^7.1.0",
|
|
69
70
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
@@ -97,5 +98,5 @@
|
|
|
97
98
|
"README.md",
|
|
98
99
|
"tsconfig.json"
|
|
99
100
|
],
|
|
100
|
-
"gitHead": "
|
|
101
|
+
"gitHead": "7a02d1e056b731aa36f72e3f8d6df941101dbd50"
|
|
101
102
|
}
|
package/src/shell/App.jsx
CHANGED
|
@@ -11,6 +11,7 @@ import { PageViewTracker } from './PageViewTracker.jsx'
|
|
|
11
11
|
import { defaultAppSettings } from './AppSettings.jsx'
|
|
12
12
|
import { Router } from '@ossy/router-react'
|
|
13
13
|
import { AppContext } from './AppContext.js'
|
|
14
|
+
import { UserSetupGuard } from './UserSetupGuard.jsx'
|
|
14
15
|
|
|
15
16
|
export const App = ({ children, language, messages, fallbackMessages, sdk: sdkOverride, ..._appSettings }) => {
|
|
16
17
|
const appSettings = { ...defaultAppSettings(), ..._appSettings }
|
|
@@ -43,7 +44,9 @@ export const App = ({ children, language, messages, fallbackMessages, sdk: sdkOv
|
|
|
43
44
|
<WorkspaceAppSettingsSync />
|
|
44
45
|
<Router {...appSettings} pages={appSettings.pages || []}>
|
|
45
46
|
<PageViewTracker />
|
|
46
|
-
|
|
47
|
+
<UserSetupGuard>
|
|
48
|
+
{children}
|
|
49
|
+
</UserSetupGuard>
|
|
47
50
|
{appSettings.devMode && <ThemeEditor />}
|
|
48
51
|
</Router>
|
|
49
52
|
</WorkspaceProvider>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useEffect } from 'react'
|
|
2
2
|
import { useRouter } from '@ossy/router-react'
|
|
3
3
|
import { useSdk } from '@ossy/sdk-react'
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { getOrCreateVisitorId } from './visitor-id.js'
|
|
5
|
+
|
|
6
|
+
const CreatePageView = { id: '@ossy/analytics/actions/create-page-view' }
|
|
6
7
|
|
|
7
8
|
/** Survives Strict Mode remounts so the same navigation is not double-counted. */
|
|
8
9
|
let lastRecordedKey = null
|
|
@@ -10,23 +11,22 @@ let lastRecordedAt = 0
|
|
|
10
11
|
const DEDUPE_MS = 1500
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* Emit a page-view
|
|
14
|
-
*
|
|
14
|
+
* Emit a page-view on client navigations (anonymous and signed-in).
|
|
15
|
+
* Public ingest on `@ossy/analytics` — invoked by id so the shell does not
|
|
16
|
+
* depend on that feature package. Failures are silent.
|
|
15
17
|
*/
|
|
16
18
|
export function PageViewTracker () {
|
|
17
19
|
const router = useRouter()
|
|
18
20
|
const sdk = useSdk()
|
|
19
|
-
const { data: workspace } = sdk.read(GetWorkspace)
|
|
20
21
|
|
|
21
22
|
useEffect(() => {
|
|
22
23
|
if (typeof window === 'undefined') return
|
|
23
|
-
if (!workspace?.id) return
|
|
24
24
|
|
|
25
25
|
const path = window.location.pathname || '/'
|
|
26
26
|
const section = window.location.hash
|
|
27
27
|
? window.location.hash.replace('#', '')
|
|
28
28
|
: undefined
|
|
29
|
-
const key = `${
|
|
29
|
+
const key = `${path}:${section || ''}:${router.language || ''}`
|
|
30
30
|
const now = Date.now()
|
|
31
31
|
if (lastRecordedKey === key && now - lastRecordedAt < DEDUPE_MS) return
|
|
32
32
|
lastRecordedKey = key
|
|
@@ -37,9 +37,9 @@ export function PageViewTracker () {
|
|
|
37
37
|
section,
|
|
38
38
|
language: router.language,
|
|
39
39
|
referrer: typeof document !== 'undefined' ? (document.referrer || undefined) : undefined,
|
|
40
|
-
|
|
40
|
+
visitorId: getOrCreateVisitorId(),
|
|
41
41
|
}).catch(() => {})
|
|
42
|
-
}, [sdk, router.href, router.language
|
|
42
|
+
}, [sdk, router.href, router.language])
|
|
43
43
|
|
|
44
44
|
return null
|
|
45
45
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { GetCurrentUser } from '@ossy/authentication'
|
|
3
|
+
import { CompleteUserNamePrompt, outstandingUserRequirements } from '@ossy/users'
|
|
4
|
+
import { useSdk, AsyncStatus } from '@ossy/sdk-react'
|
|
5
|
+
import { useApp } from './AppContext.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* After auth, block the current page until outstanding user requirements are met.
|
|
9
|
+
* Anonymous visits (public booking, sign-in, invite verify) are unchanged:
|
|
10
|
+
* `isAuthenticated` is still false on those first loads.
|
|
11
|
+
*/
|
|
12
|
+
export function UserSetupGuard ({ children }) {
|
|
13
|
+
const app = useApp()
|
|
14
|
+
const sdk = useSdk()
|
|
15
|
+
const enabled = Boolean(app?.isAuthenticated)
|
|
16
|
+
const { status, data: user, refetch } = sdk.read(GetCurrentUser, undefined, { enabled })
|
|
17
|
+
|
|
18
|
+
if (!enabled) return children
|
|
19
|
+
if (status !== AsyncStatus.Success || !user) return children
|
|
20
|
+
|
|
21
|
+
const nextRequirement = outstandingUserRequirements(user)[0]
|
|
22
|
+
if (nextRequirement === 'name') {
|
|
23
|
+
return <CompleteUserNamePrompt onCompleted={() => refetch()} />
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return children
|
|
27
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveActionsUrl } from '@ossy/sdk'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Resolve integration endpoint URLs from app context and browser origin.
|
|
3
5
|
*
|
|
@@ -19,14 +21,9 @@ export function resolveEndpoints (app) {
|
|
|
19
21
|
: ''
|
|
20
22
|
|
|
21
23
|
const normalizedApi = apiUrl.replace(/\/$/, '')
|
|
22
|
-
let actionsUrl =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
actionsUrl = `${normalizedApi}/actions`
|
|
26
|
-
} else if (normalizedApi.startsWith('/')) {
|
|
27
|
-
actionsUrl = appOrigin ? `${appOrigin}${normalizedApi}/actions` : `${normalizedApi}/actions`
|
|
28
|
-
} else if (appOrigin) {
|
|
29
|
-
actionsUrl = `${appOrigin}/actions`
|
|
24
|
+
let actionsUrl = resolveActionsUrl(normalizedApi)
|
|
25
|
+
if (appOrigin && actionsUrl.startsWith('/')) {
|
|
26
|
+
actionsUrl = `${appOrigin}${actionsUrl}`
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
const mcpUrl = appOrigin ? `${appOrigin}/mcp` : '/mcp'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const STORAGE_KEY = 'ossy-visitor-id'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stable anonymous visitor id for page-view ingest (unique visitors).
|
|
5
|
+
* Returns undefined when storage is unavailable (SSR, blocked cookies).
|
|
6
|
+
*
|
|
7
|
+
* @param {{ getItem?: Function, setItem?: Function } | null} [storage]
|
|
8
|
+
* @returns {string | undefined}
|
|
9
|
+
*/
|
|
10
|
+
export function getOrCreateVisitorId (storage) {
|
|
11
|
+
const store = storage ?? (typeof globalThis !== 'undefined' ? globalThis.localStorage : null)
|
|
12
|
+
if (!store?.getItem || !store?.setItem) return undefined
|
|
13
|
+
try {
|
|
14
|
+
const existing = store.getItem(STORAGE_KEY)
|
|
15
|
+
if (existing) return existing
|
|
16
|
+
const id = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
17
|
+
? crypto.randomUUID()
|
|
18
|
+
: `${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
19
|
+
store.setItem(STORAGE_KEY, id)
|
|
20
|
+
return id
|
|
21
|
+
} catch {
|
|
22
|
+
return undefined
|
|
23
|
+
}
|
|
24
|
+
}
|