@peax/consent-core 0.6.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/LICENSE +201 -0
- package/README.md +207 -0
- package/dist/ConsentEnforcementError-Bn_ayWJI.js +16 -0
- package/dist/assertStorageIdentifier-BQuj-9bp.js +7 -0
- package/dist/createConsentIframe-Bw-cl--m.js +93 -0
- package/dist/createConsentRequestScope-D7U8uK6m.js +58 -0
- package/dist/createConsentScript-C_4ecSTX.js +102 -0
- package/dist/createDomGuard-CuIEC0HP.js +101 -0
- package/dist/enforcement/axios.d.ts +222 -0
- package/dist/enforcement/axios.js +92 -0
- package/dist/enforcement/fetch.d.ts +222 -0
- package/dist/enforcement/fetch.js +40 -0
- package/dist/enforcement/iframe.d.ts +245 -0
- package/dist/enforcement/iframe.js +2 -0
- package/dist/enforcement/integration.d.ts +230 -0
- package/dist/enforcement/integration.js +181 -0
- package/dist/enforcement/markup.d.ts +214 -0
- package/dist/enforcement/markup.js +300 -0
- package/dist/enforcement/posthog.d.ts +68 -0
- package/dist/enforcement/posthog.js +96 -0
- package/dist/enforcement/request-rules.d.ts +248 -0
- package/dist/enforcement/request-rules.js +434 -0
- package/dist/enforcement/script.d.ts +240 -0
- package/dist/enforcement/script.js +2 -0
- package/dist/index.d.ts +258 -0
- package/dist/index.js +445 -0
- package/dist/storage/localforage.d.ts +23 -0
- package/dist/storage/localforage.js +26 -0
- package/dist/storage/sync.d.ts +278 -0
- package/dist/storage/sync.js +370 -0
- package/dist/storage.d.ts +77 -0
- package/dist/storage.js +207 -0
- package/dist/validate-D16G7mdG.js +15 -0
- package/package.json +128 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# @peax/consent-core
|
|
2
|
+
|
|
3
|
+
[Peax Consent](../../README.md) · [Documentation](../../docs/README.md) · [React banner](../consent-banner-react/README.md)
|
|
4
|
+
|
|
5
|
+
Framework-independent consent validation, decisions, and an in-memory runtime with immutable snapshots and subscriptions.
|
|
6
|
+
|
|
7
|
+
> [!NOTE]
|
|
8
|
+
> The root API evaluates and records decisions; opt-in enforcement subpaths control explicitly guarded operations. The React banner does not depend on it; connect the banner through your application's callbacks.
|
|
9
|
+
|
|
10
|
+
## Create consent
|
|
11
|
+
|
|
12
|
+
`createConsent(input)` is the only runtime export from the root entry. Explicit persistence is available separately through `/storage` and `/storage/localforage`; see [Consent storage](../../docs/consent-storage.md). Opt-in request enforcement is available through `/enforcement/fetch` and `/enforcement/axios`; see [Consent-controlled fetch](../../docs/consent-fetch.md) and [Consent-controlled Axios](../../docs/consent-axios.md). The consent factory validates decoded `{ currentAppPolicy, initialState }` data and returns `ValidationResult<ConsentRuntime>`: either `{ success: true, data }` or `{ success: false, issues }`. Both fields are required; malformed saved data is never replaced with an empty default.
|
|
13
|
+
|
|
14
|
+
Opt-in external script/iframe loading is available through `/enforcement/script` and `/enforcement/iframe`; see [Consent-controlled scripts and iframes](../../docs/consent-dom.md). These loaders own only their explicitly created nodes. Removing a script cannot undo its execution or shut down its SDK.
|
|
15
|
+
|
|
16
|
+
Additional subpaths provide [integration lifecycles and a narrowly scoped PostHog helper](../../docs/consent-integrations.md) (`/enforcement/integration`, `/enforcement/posthog`) plus [opt-in marked HTML](../../docs/consent-markup.md) (`/enforcement/markup`). These do not add root runtime exports. The separate [React consent bindings](../../docs/consent-react.md) depend on core; the published banner remains independent.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createConsent } from '@peax/consent-core'
|
|
20
|
+
import type { ConsentPolicy } from '@peax/consent-core'
|
|
21
|
+
|
|
22
|
+
const currentAppPolicy: ConsentPolicy = {
|
|
23
|
+
purposes: [{
|
|
24
|
+
id: 'product-usage-analysis',
|
|
25
|
+
title: 'Product usage analysis',
|
|
26
|
+
description: 'Measure application usage.',
|
|
27
|
+
}],
|
|
28
|
+
uses: [{
|
|
29
|
+
id: 'product-analytics',
|
|
30
|
+
serviceId: 'posthog',
|
|
31
|
+
categoryId: 'analytics',
|
|
32
|
+
purposeId: 'product-usage-analysis',
|
|
33
|
+
consentVersion: 1,
|
|
34
|
+
}],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const created = createConsent({ currentAppPolicy, initialState: { choices: [] } })
|
|
38
|
+
if (!created.success) throw new Error(JSON.stringify(created.issues))
|
|
39
|
+
const consent = created.data
|
|
40
|
+
|
|
41
|
+
consent.evaluate('product-analytics')
|
|
42
|
+
// { allowed: false, reason: 'undecided' }
|
|
43
|
+
|
|
44
|
+
const unsubscribe = consent.subscribe(() => {
|
|
45
|
+
console.log(consent.getSnapshot(), consent.getMismatchedConsent())
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Capture the policy when showing the prompt, not when submitting it.
|
|
49
|
+
const userConsentedPolicy = consent.getSnapshot().currentAppPolicy
|
|
50
|
+
|
|
51
|
+
// Later, in response to an explicit applied user decision:
|
|
52
|
+
const result = consent.setCategoryConsent({
|
|
53
|
+
userConsentedPolicy,
|
|
54
|
+
categoryId: 'analytics',
|
|
55
|
+
decision: 'granted',
|
|
56
|
+
})
|
|
57
|
+
if (!result.success) console.error(result.issues)
|
|
58
|
+
|
|
59
|
+
consent.evaluate('product-analytics') // { allowed: true }
|
|
60
|
+
unsubscribe()
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Method | Contract |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `getSnapshot()` | Returns frozen `{ currentAppPolicy, state }`, retaining its identity until a committed change |
|
|
66
|
+
| `evaluate(useId)` | Evaluates against the active policy and latest state |
|
|
67
|
+
| `getMismatchedConsent()` | Returns cached, deeply frozen outstanding requirements in policy order |
|
|
68
|
+
| `setConsentDecision({ userConsentedPolicy, useId, decision })` | Validates and applies a per-use decision to the latest state at the presented version |
|
|
69
|
+
| `setCategoryConsent({ userConsentedPolicy, categoryId, decision })` | Validates and atomically applies all category choices from the presented policy |
|
|
70
|
+
| `setConsentDecisions({ userConsentedPolicy, decisions })` | Validates disjoint category/use targets and publishes the complete batch once; [submission/persistence contract](../../docs/consent-decisions.md) |
|
|
71
|
+
| `updatePolicy(input)` | Validates and replaces the active policy without changing saved choices |
|
|
72
|
+
| `restoreState(input)` | Validates and replaces all choices from trusted persistence, preserving policy and recorded versions |
|
|
73
|
+
| `subscribe(listener)` | Registers a synchronous invalidation callback and returns an idempotent unsubscribe function |
|
|
74
|
+
|
|
75
|
+
The root also exports these types: `ConsentDecision`, `ConsentPurpose`, `ConsentUse`, `ConsentPolicy`, `ConsentChoice`, `ConsentState`, `ConsentEvaluation`, `ConsentRequirement`, `ValidationIssue`, `ValidationResult<T>`, `CreateConsentOptions`, `ConsentRuntime`, `ConsentSnapshot`, `ConsentDecisionCommand`, `ConsentDecisionsCommand`, and `CategoryConsentCommand`.
|
|
76
|
+
|
|
77
|
+
Pure helpers, standalone validators, schemas, and the subscription helper are internal. Deep imports are unsupported. Implementation is organized by domain:
|
|
78
|
+
|
|
79
|
+
The root also exports `ConsentDecisionsCommand`. Optional window-local fetch/XHR interception lives in [`/enforcement/request-rules`](../../docs/consent-request-rules.md); explicit per-use guards remain the primary path.
|
|
80
|
+
|
|
81
|
+
| Domain | Ownership |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `src/policy/` | `ConsentPolicy`, `ConsentPurpose`, declared `ConsentUse` values, their schemas, structural policy validation, and equality |
|
|
84
|
+
| `src/consent/` | User decisions/state, evaluation, requirements, decision commands, factory/snapshots/subscriptions, and consent-state validation |
|
|
85
|
+
| `src/storage/` | Internal codec, explicit persistence, and storage adapters |
|
|
86
|
+
| `src/enforcement/fetch/` | Explicit fetch guard, cancellation, errors, types and tests |
|
|
87
|
+
| `src/enforcement/axios/` | Explicit Axios guard, cancellation, errors, types and tests |
|
|
88
|
+
| `src/enforcement/script/` | External script loader, load readiness and owned-node cleanup |
|
|
89
|
+
| `src/enforcement/iframe/` | External iframe mounting and owned browsing-context removal |
|
|
90
|
+
| `src/enforcement/integration/` | Per-use SDK activation, teardown, status, and reload requirements |
|
|
91
|
+
| `src/enforcement/providers/posthog/` | Strict lazy PostHog and explicit existing-SDK adapters |
|
|
92
|
+
| `src/enforcement/markup/` | Inert marked-script/iframe scanning and dynamic observation |
|
|
93
|
+
| `src/enforcement/dom/` | Private shared DOM ownership/validation helpers and lifecycle tests |
|
|
94
|
+
| `src/enforcement/request/` | Private shared grant-period cancellation, permission checks, and disposal lifecycle for the request guards |
|
|
95
|
+
| `src/enforcement/_utils/` | Private shared constructor validation, signal retention, and the guard error base |
|
|
96
|
+
| `src/utils/validation.ts` | Generic schema primitives, `validateWithSchema`, `ValidationIssue`, and `ValidationResult` |
|
|
97
|
+
|
|
98
|
+
Each domain keeps its types, schemas, and tests locally. Policy does not import consent or storage. Shared validation in `utils/validation.ts` imports only Zod Mini. Consent depends on policy to evaluate user choices. The factory behavior is unchanged; the public validation types are renamed from `ConsentValidationIssue`/`ConsentValidationResult` to `ValidationIssue`/`ValidationResult` without aliases; `ConsentUse` describes an app declaration, while `ConsentChoice` describes a user's decision about it. Internal `arePoliciesEqual()` compares validated declarations structurally, including purpose/use order and every field; it does not determine scope compatibility or whether renewed consent is needed.
|
|
99
|
+
|
|
100
|
+
## Permissions and outstanding decisions
|
|
101
|
+
|
|
102
|
+
### Purpose definitions
|
|
103
|
+
|
|
104
|
+
Every policy declares `purposes: readonly ConsentPurpose[]` alongside `uses`. A purpose is `{ id, title, description }`; each use references one definition through `purposeId`. For example, PostHog and Umami audience-measurement uses can reference the same purpose while retaining distinct use IDs, versions, and choices. Sharing a purpose never shares a grant. Services performing independently gated activities need separate uses.
|
|
105
|
+
|
|
106
|
+
Purpose IDs are app-defined nonempty strings, not a closed built-in union. Custom and unused definitions are allowed; duplicate purpose IDs and undeclared references are rejected. Core does not infer purpose meaning, supply a provider catalog, or perform scope analysis. Human-readable descriptions remain necessary; an identifier is not a full disclosure or consent record.
|
|
107
|
+
|
|
108
|
+
Purpose-only edits update the snapshot and notify subscribers, but do not automatically change permission. When a purpose change alters consent scope, the integrator/platform must review every referencing use and supply the appropriate new use or `consentVersion`. Cosmetic wording changes can retain versions. Core cannot infer that distinction from text.
|
|
109
|
+
|
|
110
|
+
### Permission checks
|
|
111
|
+
|
|
112
|
+
`getMismatchedConsent()` replaces `getRequirements()` without an alias. Despite the name, it includes undecided uses as well as version-mismatched grants.
|
|
113
|
+
|
|
114
|
+
| Condition | Evaluation |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| Use is absent from the active policy, even if a saved grant exists | `allowed: false`, `reason: 'unknown-use'` |
|
|
117
|
+
| Use has no choice | `allowed: false`, `reason: 'undecided'` |
|
|
118
|
+
| Choice is denied, regardless of version | `allowed: false`, `reason: 'denied'` |
|
|
119
|
+
| Grant version differs from the current use's version | `allowed: false`, `reason: 'consent-version-mismatch'` |
|
|
120
|
+
| Grant version matches | `allowed: true` |
|
|
121
|
+
|
|
122
|
+
Consent is recorded per use, not as blanket permission for a service or category. Separate uses of one service can have independent decisions and categories. `consentVersion` identifies one use's consent scope, independently of package versions or other uses. Both older and newer mismatched grants are blocked. Core does not calculate versions, infer semantic scope compatibility, or determine which changes legally require renewed consent.
|
|
123
|
+
|
|
124
|
+
A policy update alone does not invalidate unchanged uses. Wording-only changes can preserve a version when they preserve scope. A mismatch does not erase or retroactively revoke the saved decision; it means that decision does not authorize the current version. Core cannot partially authorize an expanded use: independently gated activities need separate uses.
|
|
125
|
+
|
|
126
|
+
`getMismatchedConsent()` returns readonly `ConsentRequirement` values with `useId`, `categoryId`, the current `consentVersion`, and reason `undecided` or `consent-version-mismatch`. Matching grants and all denials are omitted. Removed-use choices are ignored without deletion. Empty policies produce an empty list.
|
|
127
|
+
|
|
128
|
+
An empty result is not blanket permission: all-denied states also have no outstanding decisions. Use `evaluate()` for permission checks. Requirements are derived data, not persisted state, UI commands, or mandatory user actions. The application decides whether and when to prompt.
|
|
129
|
+
|
|
130
|
+
## Decisions and policy changes
|
|
131
|
+
|
|
132
|
+
`currentAppPolicy` is the active app policy supplied at creation or through `updatePolicy()`. `userConsentedPolicy` is the policy scope shown for the submitted decision, including a denial; its name does not mean every use was granted. Capture it when opening the prompt. It is command input, not a second snapshot field or a stored historical policy. See the [consent terminology dictionary](../../docs/consent-terminology.md).
|
|
133
|
+
|
|
134
|
+
All mutation methods return `ValidationResult<ConsentSnapshot>`. Decisions must be exactly `granted` or `denied`; no choice means undecided.
|
|
135
|
+
|
|
136
|
+
Each decision must explicitly carry the policy shown to the user. The runtime validates that policy's structure, not proof that it was displayed or approved. It applies decisions to the latest state using the presented versions while leaving the active policy in place. If the policy changes while a prompt is open, accepting the older prompt cannot authorize unseen new uses or changed versions.
|
|
137
|
+
|
|
138
|
+
A category decision creates individual choices only for that category's uses in the presented policy. A future member does not inherit a grant or denial. Use `setConsentDecision()` when the prompt covers just one new or changed use. Never replay a previous acceptance after a policy change or treat UI drafts as applied decisions.
|
|
139
|
+
|
|
140
|
+
Decisions preserve unrelated and removed-use choices and existing choice order; new choices append in presented-policy order. Removed uses remain blocked by active-policy evaluation. `updatePolicy()` preserves every saved choice, including denials and mismatched versions.
|
|
141
|
+
|
|
142
|
+
`restoreState(unknown)` validates and atomically restores a trusted saved choices record into an existing runtime. It does not replace `currentAppPolicy`, change consent versions, perform I/O, or constitute a new user decision. Malformed records change nothing; equal choices preserve snapshot identity and do not notify. Existing guards respond to effective restorations through their existing subscriptions. The optional `/storage/sync` entry uses this method for [cross-tab synchronization](../../docs/consent-sync.md), with explicit save-then-notify and protection for unsaved local decisions.
|
|
143
|
+
|
|
144
|
+
This trusted-persistence method intentionally permits full state replacement, replacing the earlier no-arbitrary-replacement invariant. It retains unknown/removed-use choices for compatibility, but evaluation still blocks unknown uses and mismatched grants. Validation does not prove that the user made those choices; applications must provide trusted saved records and use the presented-policy decision commands for new user input.
|
|
145
|
+
|
|
146
|
+
Malformed commands or policies return issues without changing state, references, or notifications. Structurally valid commands targeting a use or category absent from their presented policy throw `Error` without changing the runtime.
|
|
147
|
+
|
|
148
|
+
## Validation and snapshots
|
|
149
|
+
|
|
150
|
+
Creation validates both policy and initial state. Decision methods validate the entire command and presented policy; `updatePolicy()` validates the replacement policy. There are no public standalone validation methods.
|
|
151
|
+
|
|
152
|
+
- Objects and arrays must have the declared structure; unexpected fields are rejected.
|
|
153
|
+
- Policy/state identifiers, purpose titles, and descriptions must contain a non-whitespace character. Values are preserved exactly, without trimming, conversion, or coercion.
|
|
154
|
+
- Versions must be positive safe integers.
|
|
155
|
+
- Policy purpose IDs, policy use IDs, and state choice use IDs must each be unique, even for identical duplicates. Every use's `purposeId` must exactly match a declared purpose ID.
|
|
156
|
+
- Empty arrays are valid; missing arrays are not defaulted.
|
|
157
|
+
- Initial-state validation is independent of the policy and preserves removed-use and mismatched choices.
|
|
158
|
+
|
|
159
|
+
Issues contain a `code`, string/number `path` segments, and a `message`, not raw Zod errors or copies of rejected data. Nested creation issues retain `currentAppPolicy` or `initialState` path prefixes. Inputs must be decoded JSON-like data, not JSON strings or arbitrary executable objects; caller-defined throwing getters/proxies are not caught. Validation does not authenticate saved choices or establish consent evidence.
|
|
160
|
+
|
|
161
|
+
Snapshots and requirements are detached and frozen at every object/array level. The runtime's method object is frozen too. Caller inputs are neither mutated nor frozen. Methods are stable and work without `this` binding. Instances are independent; do not share a mutable instance across unrelated server requests or users.
|
|
162
|
+
|
|
163
|
+
Identical decision/version updates and structurally equivalent policies are no-ops: snapshot/requirements identity is retained and subscribers are not notified. Policy property insertion order is ignored; purpose/use array order and every definition/declaration field remain significant.
|
|
164
|
+
|
|
165
|
+
## Subscriptions
|
|
166
|
+
|
|
167
|
+
Effective changes atomically publish the new snapshot and requirements before synchronous notification. A category update publishes once.
|
|
168
|
+
|
|
169
|
+
Subscribers are not called at registration or on reads. Duplicate registrations are independent. Notifications follow registration order; removed listeners are skipped, and listeners added during delivery join the next pass.
|
|
170
|
+
|
|
171
|
+
Reentrant mutations commit immediately and request another notification pass instead of recursively invoking listeners. Notifications mean "read the latest snapshot," not a history of every intermediate transition. A mutation's success result contains its own committed snapshot, which may precede a later reentrant change. Subscribers must not unconditionally write on every notification.
|
|
172
|
+
|
|
173
|
+
Synchronous listener failures do not roll back a commit or skip other active listeners. After notification passes finish, the originating mutation throws an `AggregateError` containing those failures. Inspect the current snapshot rather than assuming the mutation failed to apply. Subscribers handle their own asynchronous errors; callback return values are ignored.
|
|
174
|
+
|
|
175
|
+
## Migration from standalone functions
|
|
176
|
+
|
|
177
|
+
Policies now require `purposes`, even when empty, and uses require `purposeId` instead of `purpose`. Move existing purpose text into a declared definition's `description`, supply a `title` and stable `id`, and reference that ID from the use. There are no legacy aliases or automatic conversions. Preserve existing use IDs and versions when this is only a structural migration of unchanged scope. The V1 stored choices format is unchanged and contains no purpose definitions.
|
|
178
|
+
|
|
179
|
+
Creation and snapshots now use `currentAppPolicy` instead of `policy`; decision commands use `userConsentedPolicy` instead of `presentedPolicy`. The old fields are rejected, with no compatibility aliases. `ConsentPolicy` and `updatePolicy()` retain their names; decision and evaluation behavior is unchanged.
|
|
180
|
+
|
|
181
|
+
Replace standalone evaluation and requirements calls with `consent.evaluate(useId)` and `consent.getMismatchedConsent()`. Replace standalone setters with the corresponding instance methods, passing `userConsentedPolicy` explicitly instead of `policy` and `state`. Pass decoded policy/state into `createConsent()`; creation and mutations perform validation.
|
|
182
|
+
|
|
183
|
+
The former evaluation reason `outdated` is `consent-version-mismatch`, with no legacy alias. Update exhaustive switches and reason comparisons.
|
|
184
|
+
|
|
185
|
+
## Internal persisted format
|
|
186
|
+
|
|
187
|
+
The storage domain contains internal `encodeConsentState(unknown)` and `decodeConsentState(unknown)` helpers. These are not root exports or supported deep imports. `createConsentStorage(adapter)` exposes explicit async `load()`/`save(state)` through `@peax/consent-core/storage`, reusing this codec without changing the synchronous consent factory. The same subpath exports `createLocalStorageAdapter` and `createFetchStorageAdapter`; `/storage/localforage` exports `createLocalForageAdapter` with a lazy optional peer. Apps still decide when to load/save. See [Consent storage](../../docs/consent-storage.md) for examples, results, HTTP protocol, and failure/ordering guarantees.
|
|
188
|
+
|
|
189
|
+
Version 1 is a strict JSON record containing only `{ formatVersion: 1, state: { choices: [...] } }`. `formatVersion` identifies the wire structure, independently of each choice's `consentVersion`. Neither policy, derived permissions, nor requirements are persisted. This is preference state, not authenticated consent evidence or a policy history.
|
|
190
|
+
|
|
191
|
+
Encoding validates decoded JSON-like state before serializing. Decoding requires a string, parses JSON, checks the positive safe integer format version, then validates the original record using its strict version-specific schema. Both use the existing `ValidationResult` contract. Non-string input returns `invalid_type`; broken JSON returns `invalid_json`; an unsupported positive safe integer version returns `unsupported_format_version` at `['formatVersion']`. Missing/malformed versions and malformed V1 records return structural issues. Nested state issues retain their `state` prefix; rejected values and raw parsing errors are not returned.
|
|
192
|
+
|
|
193
|
+
Empty choices are valid. Duplicate choices, unexpected fields, and invalid decisions or versions are rejected without coercion, repair, or an empty-state fallback. Unknown format versions are not interpreted as V1. Absence of a stored record is a separate caller concern; the codec does not delete or overwrite failed records.
|
|
194
|
+
|
|
195
|
+
Successful decoding returns detached, deeply frozen state. Choice order, exact IDs, grants, denials, removed-use choices, and mismatched consent versions are preserved. Only evaluation against `currentAppPolicy` determines permission after restoration. The codec performs no I/O, runtime mutations, or subscription notifications. Expiry, migrations and automatic saving remain deferred. Cross-tab synchronization is separately opt-in under `/storage/sync`; V1's wire shape is unchanged and must remain stable if the in-memory model changes later.
|
|
196
|
+
|
|
197
|
+
## Boundaries and package cost
|
|
198
|
+
|
|
199
|
+
Storage I/O and cross-tab synchronization are opt-in through separate storage entries; no Peax service is required. Explicit request blocking/cancellation, script/iframe loading, integration lifecycles/PostHog shutdown hooks, and marked-HTML scanning use their own subpaths. Unmarked resources remain outside DOM guard scope; optional window-local fetch/XHR interception is provided separately by `/enforcement/request-rules`. There is no automatic saving, automatic banner opening or React code in Core; the separate React bindings remain optional. The internal codec only converts state and JSON. Subscriptions are not an audit log. Tracker classification, legal conclusions and policy approval remain outside Core.
|
|
200
|
+
|
|
201
|
+
Zod Mini is an external runtime dependency of the factory. The reviewed 4 KB root ceiling excludes Zod; packed-consumer verification separately reports the complete factory bundle including dependencies. The former pure-function-only and standalone-validation bundle profiles no longer describe supported public APIs. No global Zod configuration or locale is installed.
|
|
202
|
+
|
|
203
|
+
Core now provides persistence adapters and framework-independent fetch/Axios guards. The additive pre-release `/enforcement/fetch` entry exports `createConsentFetch`, `ConsentFetchError`, and the readonly `ConsentFetch`, `ConsentFetchOptions`, and `ConsentFetchErrorReason` types. `/enforcement/axios` similarly exports `createConsentAxios`, `ConsentAxiosError`, `ConsentAxios`, `ConsentAxiosOptions`, and `ConsentAxiosErrorReason`; Axios `^1.20.0` is an optional external peer. The additive atomic submission API has a reviewed 4 KB root ceiling; optional request rules have a reviewed 6 KB closure ceiling. Size checks include shared output chunks per entry, with unchanged separate budgets for storage, localForage, dependency-free fetch (2.25 KB), and Axios enforcement (2.75 KB excluding Axios). Clean consumers verify SSR-safe imports with and without optional peers, declarations, browser persistence, and request enforcement. See the canonical [package contracts](../../docs/engineering/package-contracts.md) before extending the API.
|
|
204
|
+
|
|
205
|
+
The additive `/enforcement/script` and `/enforcement/iframe` entries expose `createConsentScript`/`createConsentIframe`, their error classes, and readonly guard, factory options, load/mount options, handle and error-reason types. They add no dependencies and have 3 KB/2.75 KB Brotli budgets respectively, including their shared DOM and internal chunks. Browser tests cover script execution/storage/SRI, cross-origin iframe activity, ownership cleanup and explicit script-shutdown limitations. Full usage and pre-release API notes are in [the DOM guard guide](../../docs/consent-dom.md).
|
|
206
|
+
|
|
207
|
+
Pre-release correctness fixes: integration/markup listener failures no longer interrupt lifecycle cleanup; failed markup startup rolls back all managed resources while retaining script reload/no-replay state. Single-use/category commands reject blank identifiers through validation issues instead of unknown-target exceptions. Public exports and existing size budgets are unchanged. See [integration notification handling](../../docs/consent-integrations.md) and [markup rollback](../../docs/consent-markup.md).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/enforcement/_utils/ConsentEnforcementError.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared shape for enforcement guard errors. Only the concrete classes are public: each subpath
|
|
4
|
+
* exports exactly one of them, so consumers keep their own `instanceof` and literal `name` type.
|
|
5
|
+
*/
|
|
6
|
+
var ConsentEnforcementError = class extends Error {
|
|
7
|
+
useId;
|
|
8
|
+
reason;
|
|
9
|
+
constructor(message, useId, reason) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.useId = useId;
|
|
12
|
+
this.reason = reason;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { ConsentEnforcementError as t };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/storage/assertStorageIdentifier.ts
|
|
2
|
+
/** Adapter configuration is typed programmer input; persisted data uses the codec. */
|
|
3
|
+
function assertStorageIdentifier(value, label = "storage key or URL") {
|
|
4
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`Expected a nonempty ${label}.`);
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { assertStorageIdentifier as t };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { t as ConsentEnforcementError } from "./ConsentEnforcementError-Bn_ayWJI.js";
|
|
2
|
+
import { a as setReferrerPolicy, i as requireText, n as insertGuarded, o as setStringAttribute, r as requireParent, s as sourceUrl, t as createDomGuard } from "./createDomGuard-CuIEC0HP.js";
|
|
3
|
+
//#region src/enforcement/iframe/ConsentIframeError.ts
|
|
4
|
+
var ConsentIframeError = class extends ConsentEnforcementError {
|
|
5
|
+
name = "ConsentIframeError";
|
|
6
|
+
constructor(useId, reason) {
|
|
7
|
+
super(`Consent iframe blocked (${reason}).`, useId, reason);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/enforcement/iframe/iframePresentation.ts
|
|
12
|
+
function readIframePresentation({ title, width, height }) {
|
|
13
|
+
requireText(title, "iframe title");
|
|
14
|
+
for (const [name, value] of [["width", width], ["height", height]]) if (value !== void 0 && typeof value !== "string") throw new TypeError(`Expected a string ${name}.`);
|
|
15
|
+
return {
|
|
16
|
+
title,
|
|
17
|
+
width,
|
|
18
|
+
height
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function applyIframePresentation(element, options) {
|
|
22
|
+
element.title = options.title;
|
|
23
|
+
for (const name of ["width", "height"]) {
|
|
24
|
+
const value = options[name];
|
|
25
|
+
if (value === void 0) element.removeAttribute(name);
|
|
26
|
+
else setStringAttribute(element, name, value);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/enforcement/iframe/createIframeElement.ts
|
|
31
|
+
function createIframeElement(options) {
|
|
32
|
+
const presentation = readIframePresentation(options);
|
|
33
|
+
const { src, parent, sandbox, allow, referrerPolicy, loading = "eager" } = options;
|
|
34
|
+
requireParent(parent);
|
|
35
|
+
if (loading !== "eager" && loading !== "lazy") throw new TypeError("Expected eager or lazy loading.");
|
|
36
|
+
const url = sourceUrl(src, parent);
|
|
37
|
+
const element = parent.ownerDocument.createElement("iframe");
|
|
38
|
+
applyIframePresentation(element, presentation);
|
|
39
|
+
setStringAttribute(element, "sandbox", sandbox);
|
|
40
|
+
setStringAttribute(element, "allow", allow);
|
|
41
|
+
setStringAttribute(element, "loading", loading);
|
|
42
|
+
setReferrerPolicy(element, referrerPolicy);
|
|
43
|
+
return {
|
|
44
|
+
element,
|
|
45
|
+
parent,
|
|
46
|
+
url
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/enforcement/iframe/createConsentIframe.ts
|
|
51
|
+
/** Owns only frames explicitly mounted through this guard; never scans or patches the page. */
|
|
52
|
+
function createConsentIframe({ consent, useId }) {
|
|
53
|
+
const owner = createDomGuard(consent, useId, (reason) => new ConsentIframeError(useId, reason));
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
mount(options) {
|
|
56
|
+
const check = owner.begin();
|
|
57
|
+
const { element, parent, url } = createIframeElement(options);
|
|
58
|
+
check();
|
|
59
|
+
let removed = false;
|
|
60
|
+
const remove = () => {
|
|
61
|
+
if (removed) return;
|
|
62
|
+
removed = true;
|
|
63
|
+
forget();
|
|
64
|
+
element.remove();
|
|
65
|
+
};
|
|
66
|
+
const forget = owner.track(remove);
|
|
67
|
+
try {
|
|
68
|
+
insertGuarded(element, parent, url, check);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
remove();
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
const checkMounted = () => {
|
|
74
|
+
check();
|
|
75
|
+
if (removed) throw new ConsentIframeError(useId, "removed");
|
|
76
|
+
};
|
|
77
|
+
return Object.freeze({
|
|
78
|
+
element,
|
|
79
|
+
remove,
|
|
80
|
+
updatePresentation(options) {
|
|
81
|
+
checkMounted();
|
|
82
|
+
const presentation = readIframePresentation(options);
|
|
83
|
+
checkMounted();
|
|
84
|
+
applyIframePresentation(element, presentation);
|
|
85
|
+
checkMounted();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
dispose: owner.dispose
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { ConsentIframeError as n, createConsentIframe as t };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//#region src/enforcement/_utils/signalRetention.ts
|
|
2
|
+
/**
|
|
3
|
+
* Keeps a composed AbortSignal reachable from the native objects it guards.
|
|
4
|
+
*
|
|
5
|
+
* WebKit can lose a composed signal after headers when JS no longer retains it, so the signal is
|
|
6
|
+
* weakly associated with its response/body instead of being held in a permanent per-guard request
|
|
7
|
+
* list. Weak keys never keep completed responses or streams alive, and the association is only
|
|
8
|
+
* ever written: it exists to retain, not to be read back.
|
|
9
|
+
*
|
|
10
|
+
* The observable guarantee (cancellation stays connected for response bodies, clones and readers
|
|
11
|
+
* after headers resolve) is verified by the browser fixtures, which cannot be reproduced in jsdom.
|
|
12
|
+
*/
|
|
13
|
+
var signals = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
/** Retains `signal` for as long as any supplied owner stays reachable. Absent owners are skipped. */
|
|
15
|
+
function retainSignal(signal, ...owners) {
|
|
16
|
+
for (const owner of owners) if (owner) signals.set(owner, signal);
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/enforcement/request/createConsentRequestScope.ts
|
|
20
|
+
/** Private grant-period cancellation shared by request guards, not transport dispatch. */
|
|
21
|
+
function createConsentRequestScope(consent, useId, createError) {
|
|
22
|
+
let disposed = false;
|
|
23
|
+
let controller;
|
|
24
|
+
function cancel(error) {
|
|
25
|
+
const previous = controller;
|
|
26
|
+
controller = void 0;
|
|
27
|
+
previous?.abort(error);
|
|
28
|
+
}
|
|
29
|
+
function evaluate() {
|
|
30
|
+
const result = consent.evaluate(useId);
|
|
31
|
+
if (!result.allowed) cancel(createError(result.reason));
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
function assertAllowed() {
|
|
35
|
+
if (disposed) throw createError("disposed");
|
|
36
|
+
const result = evaluate();
|
|
37
|
+
if (!result.allowed) throw createError(result.reason);
|
|
38
|
+
}
|
|
39
|
+
const unsubscribe = consent.subscribe(() => {
|
|
40
|
+
if (!disposed) evaluate();
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
assertAllowed,
|
|
44
|
+
begin() {
|
|
45
|
+
assertAllowed();
|
|
46
|
+
controller ??= new AbortController();
|
|
47
|
+
return controller.signal;
|
|
48
|
+
},
|
|
49
|
+
dispose() {
|
|
50
|
+
if (disposed) return;
|
|
51
|
+
disposed = true;
|
|
52
|
+
unsubscribe();
|
|
53
|
+
cancel(createError("disposed"));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { retainSignal as n, createConsentRequestScope as t };
|