@frontera-sdk/core 1.50.26 → 1.50.28
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 +21 -0
- package/package.json +1 -1
- package/src/app-session.ts +3 -0
- package/src/bridge-client.ts +16 -1
- package/src/bridge-protocol.ts +26 -2
- package/src/create-frontera-app.tsx +12 -0
package/README.md
CHANGED
|
@@ -27,6 +27,27 @@ own routing themselves.
|
|
|
27
27
|
|
|
28
28
|
## Entry points
|
|
29
29
|
|
|
30
|
+
### Embedded application lifecycle
|
|
31
|
+
|
|
32
|
+
`useFronteraApp()` exposes `active` and `setUnsavedChanges(dirty)`. Use `active`
|
|
33
|
+
to pause optional visible-only work; an inactive app may remain mounted, with
|
|
34
|
+
its credentials refreshed. Inactivity does not cancel server-side operations.
|
|
35
|
+
|
|
36
|
+
Report local draft changes explicitly, and report `false` only after the draft
|
|
37
|
+
is saved or deliberately discarded. An app that never reports its state is
|
|
38
|
+
treated as unknown when the platform is asked to release its session. This API
|
|
39
|
+
does not implement saving for the app or provide recovery after a browser reload.
|
|
40
|
+
|
|
41
|
+
The injected app-host bridge reports native push, replacement, traversal and
|
|
42
|
+
fragment changes. A `frontera:host-navigate` request carries a `requestId` and
|
|
43
|
+
relative app path; the bridge replaces the child location and responds with
|
|
44
|
+
`frontera:navigation-ack` using the same ID. It does not add a second child history
|
|
45
|
+
step for a navigation the host already owns. Both directions validate the
|
|
46
|
+
expected origin and sender window. Older hosts may ignore the new lifecycle
|
|
47
|
+
messages; older app-host scripts do not support acknowledged navigation.
|
|
48
|
+
|
|
49
|
+
### Modules
|
|
50
|
+
|
|
30
51
|
| Import | What it is |
|
|
31
52
|
|---|---|
|
|
32
53
|
| `@frontera-sdk/core/react` | `FronteraAppProvider`, `useFronteraApp`, and mode types |
|
package/package.json
CHANGED
package/src/app-session.ts
CHANGED
package/src/bridge-client.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface BridgeSession {
|
|
|
22
22
|
onToken(handler: (token: string) => void): () => void
|
|
23
23
|
/** Subscribe to host palette / colour-scheme changes. */
|
|
24
24
|
onTheme(handler: (theme: HostTheme) => void): () => void
|
|
25
|
+
/** Visibility changes do not revoke credentials or imply execution is paused. */
|
|
26
|
+
onActivation(handler: (active: boolean) => void): () => void
|
|
25
27
|
dispose(): void
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -84,6 +86,8 @@ export function connectToHost(options: ConnectOptions = {}): Promise<BridgeSessi
|
|
|
84
86
|
const stateHandlers = new Set<(s: Record<string, unknown>) => void>()
|
|
85
87
|
const tokenHandlers = new Set<(t: string) => void>()
|
|
86
88
|
const themeHandlers = new Set<(t: HostTheme) => void>()
|
|
89
|
+
const activationHandlers = new Set<(active: boolean) => void>()
|
|
90
|
+
let active = true
|
|
87
91
|
|
|
88
92
|
return new Promise<BridgeSession>((resolve, reject) => {
|
|
89
93
|
let settled = false
|
|
@@ -91,7 +95,7 @@ export function connectToHost(options: ConnectOptions = {}): Promise<BridgeSessi
|
|
|
91
95
|
const send = (message: AppMessage) => window.parent.postMessage(message, parentOrigin)
|
|
92
96
|
|
|
93
97
|
const onMessage = (event: MessageEvent) => {
|
|
94
|
-
if (event.origin !== parentOrigin) return
|
|
98
|
+
if (event.origin !== parentOrigin || event.source !== window.parent) return
|
|
95
99
|
if (!isHostMessage(event.data)) return
|
|
96
100
|
const message = event.data
|
|
97
101
|
|
|
@@ -100,6 +104,7 @@ export function connectToHost(options: ConnectOptions = {}): Promise<BridgeSessi
|
|
|
100
104
|
if (settled) return
|
|
101
105
|
settled = true
|
|
102
106
|
clearTimeout(timer)
|
|
107
|
+
active = message.active ?? active
|
|
103
108
|
resolve({
|
|
104
109
|
init: message,
|
|
105
110
|
send,
|
|
@@ -115,11 +120,17 @@ export function connectToHost(options: ConnectOptions = {}): Promise<BridgeSessi
|
|
|
115
120
|
themeHandlers.add(handler)
|
|
116
121
|
return () => themeHandlers.delete(handler)
|
|
117
122
|
},
|
|
123
|
+
onActivation(handler) {
|
|
124
|
+
activationHandlers.add(handler)
|
|
125
|
+
handler(active)
|
|
126
|
+
return () => activationHandlers.delete(handler)
|
|
127
|
+
},
|
|
118
128
|
dispose() {
|
|
119
129
|
window.removeEventListener('message', onMessage)
|
|
120
130
|
stateHandlers.clear()
|
|
121
131
|
tokenHandlers.clear()
|
|
122
132
|
themeHandlers.clear()
|
|
133
|
+
activationHandlers.clear()
|
|
123
134
|
},
|
|
124
135
|
})
|
|
125
136
|
break
|
|
@@ -138,6 +149,10 @@ export function connectToHost(options: ConnectOptions = {}): Promise<BridgeSessi
|
|
|
138
149
|
handler({ tokens: message.tokens, colorScheme: message.colorScheme })
|
|
139
150
|
}
|
|
140
151
|
break
|
|
152
|
+
case 'frontera:activation':
|
|
153
|
+
active = message.active
|
|
154
|
+
for (const handler of activationHandlers) handler(message.active)
|
|
155
|
+
break
|
|
141
156
|
default:
|
|
142
157
|
break
|
|
143
158
|
}
|
package/src/bridge-protocol.ts
CHANGED
|
@@ -35,10 +35,14 @@ export interface BridgeInit {
|
|
|
35
35
|
* own default.
|
|
36
36
|
*/
|
|
37
37
|
path?: string
|
|
38
|
+
active?: boolean
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export type HostMessage =
|
|
41
42
|
| BridgeInit
|
|
43
|
+
| { type: 'frontera:host-ready' }
|
|
44
|
+
| { type: 'frontera:host-navigate'; requestId: string; path: string }
|
|
45
|
+
| { type: 'frontera:activation'; active: boolean }
|
|
42
46
|
| { type: 'frontera:state'; state: Record<string, unknown> }
|
|
43
47
|
| {
|
|
44
48
|
type: 'frontera:theme'
|
|
@@ -50,7 +54,10 @@ export type HostMessage =
|
|
|
50
54
|
|
|
51
55
|
export type AppMessage =
|
|
52
56
|
| { type: 'frontera:ready' }
|
|
53
|
-
| { type: 'frontera:
|
|
57
|
+
| { type: 'frontera:route-ready'; protocol: 1 }
|
|
58
|
+
| { type: 'frontera:navigate'; path: string; mode?: 'push' | 'replace' | 'traverse' | 'hash' | 'load' }
|
|
59
|
+
| { type: 'frontera:navigation-ack'; requestId: string; path: string }
|
|
60
|
+
| { type: 'frontera:unsaved'; dirty: boolean }
|
|
54
61
|
| { type: 'frontera:resize'; height: number }
|
|
55
62
|
| { type: 'frontera:state-write'; patch: Record<string, unknown> }
|
|
56
63
|
| { type: 'frontera:error'; message: string; stack?: string }
|
|
@@ -60,6 +67,12 @@ export function isHostMessage(data: unknown): data is HostMessage {
|
|
|
60
67
|
if (!data || typeof data !== 'object') return false
|
|
61
68
|
const m = data as Record<string, unknown>
|
|
62
69
|
switch (m.type) {
|
|
70
|
+
case 'frontera:host-ready':
|
|
71
|
+
return true
|
|
72
|
+
case 'frontera:host-navigate':
|
|
73
|
+
return typeof m.requestId === 'string' && isAppPath(m.path)
|
|
74
|
+
case 'frontera:activation':
|
|
75
|
+
return typeof m.active === 'boolean'
|
|
63
76
|
case 'frontera:init':
|
|
64
77
|
return typeof m.token === 'string' && typeof m.apiBaseUrl === 'string' && typeof m.appId === 'string'
|
|
65
78
|
case 'frontera:state':
|
|
@@ -77,10 +90,16 @@ export function isAppMessage(data: unknown): data is AppMessage {
|
|
|
77
90
|
if (!data || typeof data !== 'object') return false
|
|
78
91
|
const m = data as Record<string, unknown>
|
|
79
92
|
switch (m.type) {
|
|
93
|
+
case 'frontera:unsaved':
|
|
94
|
+
return typeof m.dirty === 'boolean'
|
|
95
|
+
case 'frontera:route-ready':
|
|
96
|
+
return m.protocol === 1
|
|
97
|
+
case 'frontera:navigation-ack':
|
|
98
|
+
return typeof m.requestId === 'string' && isAppPath(m.path)
|
|
80
99
|
case 'frontera:ready':
|
|
81
100
|
return true
|
|
82
101
|
case 'frontera:navigate':
|
|
83
|
-
return
|
|
102
|
+
return isAppPath(m.path) && (m.mode === undefined || ['push', 'replace', 'traverse', 'hash', 'load'].includes(m.mode as string))
|
|
84
103
|
case 'frontera:resize':
|
|
85
104
|
return typeof m.height === 'number' && Number.isFinite(m.height)
|
|
86
105
|
case 'frontera:state-write':
|
|
@@ -93,3 +112,8 @@ export function isAppMessage(data: unknown): data is AppMessage {
|
|
|
93
112
|
return false
|
|
94
113
|
}
|
|
95
114
|
}
|
|
115
|
+
|
|
116
|
+
/** App locations are relative to the isolated app origin. */
|
|
117
|
+
function isAppPath(path: unknown): path is string {
|
|
118
|
+
return typeof path === 'string' && path.startsWith('/') && !path.startsWith('//') && !path.includes('\\')
|
|
119
|
+
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
Component,
|
|
3
3
|
StrictMode,
|
|
4
4
|
createContext,
|
|
5
|
+
useCallback,
|
|
5
6
|
useContext,
|
|
6
7
|
useEffect,
|
|
7
8
|
useState,
|
|
@@ -42,6 +43,10 @@ export interface FronteraAppValue {
|
|
|
42
43
|
/** Everything the host said at handshake, including `path`. */
|
|
43
44
|
init: BridgeInit
|
|
44
45
|
client: FronteraClient
|
|
46
|
+
/** Whether this app is the visible platform surface. */
|
|
47
|
+
active: boolean
|
|
48
|
+
/** Report local work explicitly; unreported state remains unknown to the host. */
|
|
49
|
+
setUnsavedChanges(dirty: boolean): void
|
|
45
50
|
/** Ask the platform to change its URL — the counterpart to `init.path`. */
|
|
46
51
|
navigate(path: string): void
|
|
47
52
|
}
|
|
@@ -383,11 +388,18 @@ function FronteraRoot({
|
|
|
383
388
|
)
|
|
384
389
|
|
|
385
390
|
useEffect(() => session.onTheme((theme) => applyHostTheme(theme)), [session])
|
|
391
|
+
const [active, setActive] = useState(init.active ?? true)
|
|
392
|
+
useEffect(() => session.onActivation(setActive), [session])
|
|
393
|
+
const setUnsavedChanges = useCallback((dirty: boolean) => {
|
|
394
|
+
if (mode === 'embedded') session.send({ type: 'frontera:unsaved', dirty })
|
|
395
|
+
}, [mode, session])
|
|
386
396
|
|
|
387
397
|
const value: FronteraAppValue = {
|
|
388
398
|
mode,
|
|
389
399
|
init,
|
|
390
400
|
client,
|
|
401
|
+
active,
|
|
402
|
+
setUnsavedChanges,
|
|
391
403
|
navigate: (path) => {
|
|
392
404
|
if (mode === 'embedded') session.send({ type: 'frontera:navigate', path })
|
|
393
405
|
},
|