@unitninety/site-kit 1.6.4 → 1.6.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/README.md +104 -1
- package/package.json +4 -3
- package/src/bridge/DraftBlocks.tsx +10 -0
- package/src/bridge/createBridge.ts +11 -10
- package/src/bridge/index.ts +1 -0
- package/src/bridge/nativeDraft.ts +22 -4
- package/src/bridge/nativeOrder.ts +68 -0
- package/src/bridge/registry.ts +3 -2
- package/src/bridge/selection.ts +59 -10
- package/src/bridge/useDraftContent.tsx +35 -22
- package/src/components/BlockLink.tsx +28 -0
- package/src/components/Link.tsx +8 -1
- package/src/components/block-navigation.ts +24 -0
- package/src/custom-code/fragments.ts +64 -0
- package/src/custom-code/index.ts +30 -0
- package/src/elements/blockAnchor.ts +7 -0
- package/src/elements/declaration.ts +1 -1
- package/src/elements/define.ts +1 -1
- package/src/elements/index.ts +1 -0
- package/src/elements/ops/validate.ts +9 -2
- package/src/features/client/README.md +1 -2
- package/src/features/client/SiteFeatures.tsx +15 -25
- package/src/features/client/index.ts +0 -6
- package/src/features/client/verification.ts +3 -0
- package/src/features/contracts/base.ts +1 -5
- package/src/features/contracts/index.ts +0 -20
- package/src/features/contracts/verificationConfig.ts +7 -0
- package/src/features/server/gateway.ts +1 -3
- package/src/features/server/index.ts +0 -2
- package/src/legacy/cli.mjs +2 -2
- package/src/legacy/content.ts +1 -1
- package/src/server/BlockAnchors.tsx +92 -0
- package/src/server/CustomCode.tsx +29 -0
- package/src/server/index.ts +1 -0
- package/src/server/read.ts +13 -0
- package/src/server/render.tsx +11 -5
- package/src/site/defineSite.ts +6 -3
- package/src/site/documents.ts +2 -2
- package/src/site/forms.ts +32 -0
- package/src/site/index.ts +2 -0
- package/src/site/types.ts +4 -1
- package/src/site/version.ts +1 -1
- package/src/site/wire.ts +3 -0
- package/src/features/client/ConsentRuntime.tsx +0 -95
- package/src/features/client/useConsent.ts +0 -15
- package/src/features/consent/README.md +0 -70
- package/src/features/consent/cookieScript.ts +0 -53
- package/src/features/consent/custom.ts +0 -89
- package/src/features/consent/defaults.ts +0 -40
- package/src/features/consent/drivers.ts +0 -233
- package/src/features/consent/loader.ts +0 -248
- package/src/features/consent/registry.ts +0 -398
- package/src/features/contracts/consent.ts +0 -150
- package/src/features/server/ConsentDefaults.tsx +0 -15
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
classifyManagedIntegration,
|
|
3
|
-
deniedConsentState,
|
|
4
|
-
ManagedIntegrationSchema,
|
|
5
|
-
type ConsentState,
|
|
6
|
-
type IntegrationDriver,
|
|
7
|
-
type ManagedIntegration,
|
|
8
|
-
} from '../contracts/consent'
|
|
9
|
-
import { ConsentReloadRequiredError, executableFingerprint } from './loader'
|
|
10
|
-
import { initializeConsentDefaults, type GoogleConsentWindow } from './defaults'
|
|
11
|
-
|
|
12
|
-
type BrowserWindow = GoogleConsentWindow & {
|
|
13
|
-
_uxa?: unknown[][]
|
|
14
|
-
[key: `ga-disable-${string}`]: boolean
|
|
15
|
-
}
|
|
16
|
-
type Execution = {
|
|
17
|
-
integration: ManagedIntegration
|
|
18
|
-
kind: ReturnType<typeof classifyManagedIntegration>
|
|
19
|
-
attempted: boolean
|
|
20
|
-
loaded: boolean
|
|
21
|
-
running: boolean
|
|
22
|
-
allowed: boolean
|
|
23
|
-
configured: boolean
|
|
24
|
-
uncertain: boolean
|
|
25
|
-
element?: HTMLScriptElement
|
|
26
|
-
loading?: Promise<void>
|
|
27
|
-
lastPage?: string
|
|
28
|
-
}
|
|
29
|
-
export const CONSENT_LOAD_TIMEOUT_MS = 10000
|
|
30
|
-
/** Private built-in driver. Its synchronous veto is independent of async loader completion. */
|
|
31
|
-
export function createBrowserConsentDriver(window: Window): IntegrationDriver & {
|
|
32
|
-
configure(integrations: ManagedIntegration[]): void
|
|
33
|
-
setPermission(state: ConsentState, mode: 'basic' | 'advanced'): void
|
|
34
|
-
suspend(value: boolean): void
|
|
35
|
-
page(path: string): void
|
|
36
|
-
attempted(): boolean
|
|
37
|
-
googleAttempted(): boolean
|
|
38
|
-
} {
|
|
39
|
-
const target = window as BrowserWindow
|
|
40
|
-
const records = new Map<string, Execution>()
|
|
41
|
-
let state = deniedConsentState()
|
|
42
|
-
let mode: 'basic' | 'advanced' = 'basic'
|
|
43
|
-
let suspended = false
|
|
44
|
-
let path = window.location.pathname
|
|
45
|
-
const permitted = (record: Execution) =>
|
|
46
|
-
!suspended &&
|
|
47
|
-
state.ready &&
|
|
48
|
-
state[record.integration.category] &&
|
|
49
|
-
record.allowed &&
|
|
50
|
-
(record.kind.kind !== 'ga4' || state.analytics)
|
|
51
|
-
const signal = () => {
|
|
52
|
-
if (target.__u90ConsentDefaultsV1 !== true) return
|
|
53
|
-
target.gtag?.('consent', 'update', {
|
|
54
|
-
analytics_storage: state.ready && state.analytics ? 'granted' : 'denied',
|
|
55
|
-
ad_storage: state.ready && state.marketing ? 'granted' : 'denied',
|
|
56
|
-
ad_user_data: state.ready && state.marketing ? 'granted' : 'denied',
|
|
57
|
-
ad_personalization: state.ready && state.marketing ? 'granted' : 'denied',
|
|
58
|
-
functionality_storage: state.ready && state.preferences ? 'granted' : 'denied',
|
|
59
|
-
personalization_storage: state.ready && state.preferences ? 'granted' : 'denied',
|
|
60
|
-
security_storage: 'granted',
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
const veto = (record: Execution) => {
|
|
64
|
-
if (record.kind.kind === 'ga4')
|
|
65
|
-
target[`ga-disable-${record.kind.measurementId}`] = !permitted(record)
|
|
66
|
-
}
|
|
67
|
-
const pageview = (record: Execution) => {
|
|
68
|
-
if (
|
|
69
|
-
record.kind.kind !== 'ga4' ||
|
|
70
|
-
!record.loaded ||
|
|
71
|
-
!permitted(record) ||
|
|
72
|
-
record.lastPage === path
|
|
73
|
-
)
|
|
74
|
-
return
|
|
75
|
-
record.lastPage = path
|
|
76
|
-
target.gtag!('event', 'page_view', {
|
|
77
|
-
send_to: record.kind.measurementId,
|
|
78
|
-
page_path: path,
|
|
79
|
-
page_location: new URL(path, window.location.origin).href,
|
|
80
|
-
page_title: window.document.title,
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
const optout = () => {
|
|
84
|
-
target._uxa = target._uxa || []
|
|
85
|
-
target._uxa.push(['optout'])
|
|
86
|
-
}
|
|
87
|
-
const driver = {
|
|
88
|
-
// Preserve proven teardown for executed code; policy edits never promote catalogue authority.
|
|
89
|
-
configure(integrations: ManagedIntegration[]) {
|
|
90
|
-
for (const record of records.values()) {
|
|
91
|
-
const next = integrations.find((row) => row.id === record.integration.id)
|
|
92
|
-
if (next && executableFingerprint(next) === executableFingerprint(record.integration))
|
|
93
|
-
record.integration = next
|
|
94
|
-
else {
|
|
95
|
-
record.running = false
|
|
96
|
-
record.allowed = false
|
|
97
|
-
}
|
|
98
|
-
veto(record)
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
setPermission(next: ConsentState, nextMode: 'basic' | 'advanced') {
|
|
102
|
-
const changed = JSON.stringify(state) !== JSON.stringify(next)
|
|
103
|
-
state = { ...next }
|
|
104
|
-
mode = nextMode
|
|
105
|
-
for (const record of records.values()) {
|
|
106
|
-
record.allowed = record.running && state.ready && state[record.integration.category]
|
|
107
|
-
if (!permitted(record)) record.lastPage = undefined
|
|
108
|
-
veto(record)
|
|
109
|
-
}
|
|
110
|
-
if (changed) signal()
|
|
111
|
-
for (const record of records.values()) pageview(record)
|
|
112
|
-
},
|
|
113
|
-
suspend(value: boolean) {
|
|
114
|
-
suspended = value
|
|
115
|
-
for (const record of records.values()) veto(record)
|
|
116
|
-
},
|
|
117
|
-
page(next: string) {
|
|
118
|
-
path = next
|
|
119
|
-
for (const record of records.values()) pageview(record)
|
|
120
|
-
},
|
|
121
|
-
attempted: () => [...records.values()].some((record) => record.attempted),
|
|
122
|
-
googleAttempted: () =>
|
|
123
|
-
[...records.values()].some((record) => record.attempted && record.kind.kind === 'ga4'),
|
|
124
|
-
async start(input: ManagedIntegration) {
|
|
125
|
-
const integration = ManagedIntegrationSchema.parse(input)
|
|
126
|
-
let record = records.get(integration.id)
|
|
127
|
-
if (!record) {
|
|
128
|
-
record = {
|
|
129
|
-
integration,
|
|
130
|
-
kind: classifyManagedIntegration(integration),
|
|
131
|
-
attempted: false,
|
|
132
|
-
loaded: false,
|
|
133
|
-
running: false,
|
|
134
|
-
allowed: false,
|
|
135
|
-
configured: false,
|
|
136
|
-
uncertain: false,
|
|
137
|
-
}
|
|
138
|
-
records.set(integration.id, record)
|
|
139
|
-
}
|
|
140
|
-
if (record.uncertain)
|
|
141
|
-
throw new ConsentReloadRequiredError(
|
|
142
|
-
'Reload required after uncertain integration initialization.',
|
|
143
|
-
)
|
|
144
|
-
const current = record
|
|
145
|
-
current.running = true
|
|
146
|
-
current.integration = integration
|
|
147
|
-
current.allowed = !suspended && state.ready && state[integration.category]
|
|
148
|
-
if (
|
|
149
|
-
!current.allowed &&
|
|
150
|
-
!(current.kind.kind === 'ga4' && mode === 'advanced' && state.ready && !suspended)
|
|
151
|
-
)
|
|
152
|
-
return
|
|
153
|
-
if (current.kind.kind === 'ga4') {
|
|
154
|
-
initializeConsentDefaults(window)
|
|
155
|
-
signal()
|
|
156
|
-
veto(current)
|
|
157
|
-
if (!current.configured) {
|
|
158
|
-
target.gtag!('js', new Date())
|
|
159
|
-
target.gtag!('config', current.kind.measurementId, { send_page_view: false })
|
|
160
|
-
current.configured = true
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (current.loaded) {
|
|
164
|
-
veto(current)
|
|
165
|
-
pageview(current)
|
|
166
|
-
return
|
|
167
|
-
}
|
|
168
|
-
if (!current.loading) {
|
|
169
|
-
current.loading = new Promise<void>((resolve, reject) => {
|
|
170
|
-
const script = window.document.createElement('script')
|
|
171
|
-
script.async = true
|
|
172
|
-
for (const [key, value] of Object.entries(integration.attributes ?? {}))
|
|
173
|
-
script.setAttribute(key, value)
|
|
174
|
-
if (script.id && window.document.getElementById(script.id)) {
|
|
175
|
-
reject(Error('Integration element ID is already owned.'))
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
script.src = integration.src
|
|
179
|
-
script.dataset.u90Integration = integration.id
|
|
180
|
-
current.element = script
|
|
181
|
-
const finish = () => {
|
|
182
|
-
window.clearTimeout(timer)
|
|
183
|
-
script.onload = null
|
|
184
|
-
script.onerror = null
|
|
185
|
-
}
|
|
186
|
-
const timer = window.setTimeout(() => {
|
|
187
|
-
finish()
|
|
188
|
-
current.uncertain = true
|
|
189
|
-
reject(new ConsentReloadRequiredError('Integration load timed out; reload required.'))
|
|
190
|
-
}, CONSENT_LOAD_TIMEOUT_MS)
|
|
191
|
-
script.onload = () => {
|
|
192
|
-
finish()
|
|
193
|
-
current.loaded = true
|
|
194
|
-
resolve()
|
|
195
|
-
}
|
|
196
|
-
script.onerror = () => {
|
|
197
|
-
finish()
|
|
198
|
-
script.remove()
|
|
199
|
-
current.element = undefined
|
|
200
|
-
reject(Error('Integration failed to load. Retry consent setup.'))
|
|
201
|
-
}
|
|
202
|
-
current.attempted = true
|
|
203
|
-
window.document.head.appendChild(script)
|
|
204
|
-
}).finally(() => {
|
|
205
|
-
current.loading = undefined
|
|
206
|
-
})
|
|
207
|
-
}
|
|
208
|
-
await current.loading
|
|
209
|
-
veto(current)
|
|
210
|
-
if (current.kind.kind === 'contentsquare' && !permitted(current)) optout()
|
|
211
|
-
pageview(current)
|
|
212
|
-
},
|
|
213
|
-
async stop(id: string): Promise<'stopped' | 'reload-required'> {
|
|
214
|
-
const record = records.get(id)
|
|
215
|
-
if (!record) return 'stopped'
|
|
216
|
-
record.running = false
|
|
217
|
-
record.allowed = false
|
|
218
|
-
record.lastPage = undefined
|
|
219
|
-
veto(record)
|
|
220
|
-
if (!record.attempted) return 'stopped'
|
|
221
|
-
if (record.kind.kind === 'contentsquare') optout()
|
|
222
|
-
if (
|
|
223
|
-
record.kind.kind === 'ga4' &&
|
|
224
|
-
!record.uncertain &&
|
|
225
|
-
record.integration.revocation === 'stop'
|
|
226
|
-
)
|
|
227
|
-
return 'stopped'
|
|
228
|
-
// Removing a node cannot undo downloaded or executing third-party code.
|
|
229
|
-
return 'reload-required'
|
|
230
|
-
},
|
|
231
|
-
}
|
|
232
|
-
return driver
|
|
233
|
-
}
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ConsentStateSchema,
|
|
3
|
-
type ConsentState,
|
|
4
|
-
type IntegrationDriver,
|
|
5
|
-
type ManagedIntegration,
|
|
6
|
-
} from '../contracts/consent'
|
|
7
|
-
|
|
8
|
-
export class ConsentReloadRequiredError extends Error {}
|
|
9
|
-
const OPERATION_TIMEOUT_MS = 10000
|
|
10
|
-
function bounded<T>(operation: Promise<T>, timeout: () => void): Promise<T> {
|
|
11
|
-
let timer: ReturnType<typeof setTimeout>
|
|
12
|
-
return Promise.race([
|
|
13
|
-
operation,
|
|
14
|
-
new Promise<never>((_, reject) => {
|
|
15
|
-
timer = setTimeout(() => {
|
|
16
|
-
timeout()
|
|
17
|
-
reject(new ConsentReloadRequiredError('Consent operation timed out; reload required.'))
|
|
18
|
-
}, OPERATION_TIMEOUT_MS)
|
|
19
|
-
}),
|
|
20
|
-
]).finally(() => clearTimeout(timer))
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type RecordState = {
|
|
24
|
-
integration: ManagedIntegration
|
|
25
|
-
fingerprint: string
|
|
26
|
-
desired: boolean
|
|
27
|
-
attempted: boolean
|
|
28
|
-
active: boolean
|
|
29
|
-
revoked: boolean
|
|
30
|
-
start?: Promise<void>
|
|
31
|
-
stop?: Promise<void>
|
|
32
|
-
error?: string
|
|
33
|
-
}
|
|
34
|
-
type Store = {
|
|
35
|
-
records: Map<string, RecordState>
|
|
36
|
-
reloadRequired: boolean
|
|
37
|
-
error: string | null
|
|
38
|
-
listeners: Set<() => void>
|
|
39
|
-
}
|
|
40
|
-
const stores = new WeakMap<IntegrationDriver, Store>()
|
|
41
|
-
function storeFor(driver: IntegrationDriver): Store {
|
|
42
|
-
let store = stores.get(driver)
|
|
43
|
-
if (!store) {
|
|
44
|
-
store = { records: new Map(), reloadRequired: false, error: null, listeners: new Set() }
|
|
45
|
-
stores.set(driver, store)
|
|
46
|
-
}
|
|
47
|
-
return store
|
|
48
|
-
}
|
|
49
|
-
export function executableFingerprint(integration: ManagedIntegration): string {
|
|
50
|
-
return JSON.stringify([
|
|
51
|
-
integration.src,
|
|
52
|
-
Object.entries(integration.attributes ?? {}).sort(([a], [b]) => a.localeCompare(b)),
|
|
53
|
-
])
|
|
54
|
-
}
|
|
55
|
-
/** Internal diagnostics; only the fixed loader contract is exposed by the package. */
|
|
56
|
-
export function consentLoaderStatus(driver: IntegrationDriver) {
|
|
57
|
-
const { reloadRequired, error } = storeFor(driver)
|
|
58
|
-
return { reloadRequired, error }
|
|
59
|
-
}
|
|
60
|
-
export function observeConsentLoader(driver: IntegrationDriver, listener: () => void) {
|
|
61
|
-
const listeners = storeFor(driver).listeners
|
|
62
|
-
listeners.add(listener)
|
|
63
|
-
return () => {
|
|
64
|
-
listeners.delete(listener)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function publish(store: Store) {
|
|
68
|
-
for (const listener of store.listeners) listener()
|
|
69
|
-
}
|
|
70
|
-
export function requireConsentReload(driver: IntegrationDriver, reason: string) {
|
|
71
|
-
const store = storeFor(driver)
|
|
72
|
-
store.reloadRequired = true
|
|
73
|
-
store.error = reason
|
|
74
|
-
for (const record of store.records.values()) {
|
|
75
|
-
record.desired = false
|
|
76
|
-
void stopRecord(driver, store, record).catch(() => {})
|
|
77
|
-
}
|
|
78
|
-
publish(store)
|
|
79
|
-
}
|
|
80
|
-
function stopRecord(
|
|
81
|
-
driver: IntegrationDriver,
|
|
82
|
-
store: Store,
|
|
83
|
-
record: RecordState,
|
|
84
|
-
finalize = false,
|
|
85
|
-
): Promise<void> {
|
|
86
|
-
if (!record.attempted || (record.revoked && !finalize)) return record.stop ?? Promise.resolve()
|
|
87
|
-
record.revoked = true
|
|
88
|
-
record.active = false
|
|
89
|
-
// Invoke before constructing/awaiting any queue: drivers synchronously latch their veto.
|
|
90
|
-
let operation: Promise<'stopped' | 'reload-required'>
|
|
91
|
-
try {
|
|
92
|
-
operation = driver.stop(record.integration.id)
|
|
93
|
-
} catch (cause) {
|
|
94
|
-
operation = Promise.reject(cause)
|
|
95
|
-
}
|
|
96
|
-
const stop = bounded(operation, () =>
|
|
97
|
-
requireConsentReload(driver, 'Reload to apply consent changes. Integration stop timed out.'),
|
|
98
|
-
)
|
|
99
|
-
.then(
|
|
100
|
-
(result) => {
|
|
101
|
-
if (result === 'reload-required' || record.integration.revocation === 'reload')
|
|
102
|
-
requireConsentReload(
|
|
103
|
-
driver,
|
|
104
|
-
'Reload to apply consent changes. Previously started code requires a fresh document.',
|
|
105
|
-
)
|
|
106
|
-
},
|
|
107
|
-
(cause) => {
|
|
108
|
-
requireConsentReload(
|
|
109
|
-
driver,
|
|
110
|
-
`Reload to apply consent changes. Integration stop failed: ${cause instanceof Error ? cause.message : 'unavailable'}`,
|
|
111
|
-
)
|
|
112
|
-
throw cause
|
|
113
|
-
},
|
|
114
|
-
)
|
|
115
|
-
.finally(() => {
|
|
116
|
-
if (record.stop === stop) record.stop = undefined
|
|
117
|
-
publish(store)
|
|
118
|
-
})
|
|
119
|
-
record.stop = stop
|
|
120
|
-
return stop
|
|
121
|
-
}
|
|
122
|
-
function startRecord(driver: IntegrationDriver, store: Store, record: RecordState): Promise<void> {
|
|
123
|
-
if (record.start) return record.start
|
|
124
|
-
if (!record.desired || store.reloadRequired || record.active)
|
|
125
|
-
return record.stop ?? Promise.resolve()
|
|
126
|
-
const begin = async () => {
|
|
127
|
-
if (record.stop) await record.stop
|
|
128
|
-
if (!record.desired || store.reloadRequired || record.active) return
|
|
129
|
-
record.attempted = true
|
|
130
|
-
record.revoked = false
|
|
131
|
-
record.error = undefined
|
|
132
|
-
try {
|
|
133
|
-
let expired = false
|
|
134
|
-
const operation = driver.start(record.integration)
|
|
135
|
-
void operation.then(
|
|
136
|
-
() => {
|
|
137
|
-
if (expired) void stopRecord(driver, store, record, true).catch(() => {})
|
|
138
|
-
},
|
|
139
|
-
() => {},
|
|
140
|
-
)
|
|
141
|
-
await bounded(operation, () => {
|
|
142
|
-
expired = true
|
|
143
|
-
requireConsentReload(
|
|
144
|
-
driver,
|
|
145
|
-
'Reload to apply consent changes. Integration initialization timed out.',
|
|
146
|
-
)
|
|
147
|
-
})
|
|
148
|
-
if (!record.desired || record.revoked || store.reloadRequired) {
|
|
149
|
-
await stopRecord(driver, store, record, true)
|
|
150
|
-
} else record.active = true
|
|
151
|
-
} catch (cause) {
|
|
152
|
-
if (cause instanceof ConsentReloadRequiredError) requireConsentReload(driver, cause.message)
|
|
153
|
-
record.error = cause instanceof Error ? cause.message : 'Integration initialization failed'
|
|
154
|
-
store.error = record.error
|
|
155
|
-
publish(store)
|
|
156
|
-
throw cause
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
// begin invokes start synchronously until its first await.
|
|
160
|
-
const starting = begin().finally(() => {
|
|
161
|
-
if (record.start === starting) record.start = undefined
|
|
162
|
-
publish(store)
|
|
163
|
-
})
|
|
164
|
-
record.start = starting
|
|
165
|
-
return starting
|
|
166
|
-
}
|
|
167
|
-
export function createConsentLoader(driver: IntegrationDriver): {
|
|
168
|
-
apply(state: ConsentState, integrations: ManagedIntegration[]): Promise<void>
|
|
169
|
-
dispose(): void
|
|
170
|
-
} {
|
|
171
|
-
const store = storeFor(driver)
|
|
172
|
-
let disposed = false
|
|
173
|
-
return {
|
|
174
|
-
async apply(input, integrations) {
|
|
175
|
-
if (disposed) return
|
|
176
|
-
const parsed = ConsentStateSchema.safeParse(input)
|
|
177
|
-
const state = parsed.success ? parsed.data : null
|
|
178
|
-
const incoming = new Map(integrations.map((row) => [row.id, row]))
|
|
179
|
-
const operations: Promise<void>[] = []
|
|
180
|
-
// Record *all* restrictions first, independently of asynchronous failures.
|
|
181
|
-
for (const [id, record] of store.records) {
|
|
182
|
-
const next = incoming.get(id)
|
|
183
|
-
if (next && executableFingerprint(next) !== record.fingerprint && record.attempted)
|
|
184
|
-
requireConsentReload(
|
|
185
|
-
driver,
|
|
186
|
-
'Reload to apply consent changes. An executed integration was replaced.',
|
|
187
|
-
)
|
|
188
|
-
record.desired = !store.reloadRequired && !!next && !!state?.ready && state[next.category]
|
|
189
|
-
if (next && executableFingerprint(next) === record.fingerprint) record.integration = next
|
|
190
|
-
if (!record.desired) operations.push(stopRecord(driver, store, record))
|
|
191
|
-
}
|
|
192
|
-
// A different ID must not slip past a stop that may latch document reload.
|
|
193
|
-
const pendingStops = [...store.records.values()].flatMap((record) =>
|
|
194
|
-
record.stop ? [record.stop] : [],
|
|
195
|
-
)
|
|
196
|
-
const stopped = pendingStops.length ? Promise.allSettled(pendingStops) : undefined
|
|
197
|
-
for (const integration of integrations) {
|
|
198
|
-
let record = store.records.get(integration.id)
|
|
199
|
-
if (
|
|
200
|
-
!record ||
|
|
201
|
-
(!record.attempted && executableFingerprint(integration) !== record.fingerprint)
|
|
202
|
-
) {
|
|
203
|
-
record = {
|
|
204
|
-
integration,
|
|
205
|
-
fingerprint: executableFingerprint(integration),
|
|
206
|
-
desired: false,
|
|
207
|
-
attempted: false,
|
|
208
|
-
active: false,
|
|
209
|
-
revoked: false,
|
|
210
|
-
}
|
|
211
|
-
store.records.set(integration.id, record)
|
|
212
|
-
}
|
|
213
|
-
record.desired = !store.reloadRequired && !!state?.ready && state[integration.category]
|
|
214
|
-
if (record.desired) {
|
|
215
|
-
const current = record
|
|
216
|
-
operations.push(
|
|
217
|
-
(stopped
|
|
218
|
-
? stopped.then(() => {
|
|
219
|
-
if (!disposed) return startRecord(driver, store, current)
|
|
220
|
-
})
|
|
221
|
-
: startRecord(driver, store, current)
|
|
222
|
-
).then(async () => {
|
|
223
|
-
// A regrant during an unresolved start/stop resumes only once both settled.
|
|
224
|
-
if (!disposed && current.desired && !current.active && !store.reloadRequired)
|
|
225
|
-
await startRecord(driver, store, current)
|
|
226
|
-
}),
|
|
227
|
-
)
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
const results = await Promise.allSettled(operations)
|
|
231
|
-
const failure = results.find(
|
|
232
|
-
(result): result is PromiseRejectedResult => result.status === 'rejected',
|
|
233
|
-
)
|
|
234
|
-
if (failure) throw failure.reason
|
|
235
|
-
if (!store.reloadRequired && ![...store.records.values()].some((record) => record.error))
|
|
236
|
-
store.error = null
|
|
237
|
-
publish(store)
|
|
238
|
-
},
|
|
239
|
-
dispose() {
|
|
240
|
-
if (disposed) return
|
|
241
|
-
disposed = true
|
|
242
|
-
for (const record of store.records.values()) {
|
|
243
|
-
record.desired = false
|
|
244
|
-
void stopRecord(driver, store, record).catch(() => {})
|
|
245
|
-
}
|
|
246
|
-
},
|
|
247
|
-
}
|
|
248
|
-
}
|