@adobe/alloy-node 0.1.0-beta.1
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 +169 -0
- package/package.json +32 -0
- package/src/allOptionalComponents.js +17 -0
- package/src/alloy-core.d.ts +12 -0
- package/src/components/context.js +56 -0
- package/src/components/personalization.js +99 -0
- package/src/components/requiredComponentCreators.js +17 -0
- package/src/createNodeAlloy.js +167 -0
- package/src/index.js +63 -0
- package/src/services/createNodeCookieService.js +54 -0
- package/src/services/createNodeGlobalsService.js +43 -0
- package/src/services/createNodeLegacyService.js +26 -0
- package/src/services/createNodeNetworkService.js +55 -0
- package/src/services/createNodePlatformServices.js +77 -0
- package/src/services/createNodeRuntimeService.js +26 -0
- package/src/services/createNodeStorageService.js +53 -0
- package/src/services/getHeader.js +35 -0
- package/src/services/index.js +19 -0
- package/src/services/pickForwardableHeaders.js +55 -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,169 @@
|
|
|
1
|
+
# @adobe/alloy-node
|
|
2
|
+
|
|
3
|
+
> [!WARNING]
|
|
4
|
+
> **Alpha. Only `configure`, `sendEvent`, Identity's commands, Consent, a
|
|
5
|
+
> minimal Context, and fetch-only Personalization are exercised so far.**
|
|
6
|
+
> Most other optional components (Audiences, RulesEngine, etc.) are not
|
|
7
|
+
> wired up yet.
|
|
8
|
+
|
|
9
|
+
Node.js entrypoint for the Adobe Experience Platform Web SDK.
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { createInstance } from "@adobe/alloy-node";
|
|
13
|
+
|
|
14
|
+
const alloy = createInstance();
|
|
15
|
+
await alloy.configure({
|
|
16
|
+
orgId: "...",
|
|
17
|
+
datastreamId: "...",
|
|
18
|
+
});
|
|
19
|
+
await alloy.sendEvent({ xdm: { eventType: "..." } });
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Unlike the browser bundle's `alloy("commandName", options)` calling
|
|
23
|
+
convention, `createInstance`/`createCustomInstance` return an object with
|
|
24
|
+
real methods (`configure`, `sendEvent`, `getIdentity`,
|
|
25
|
+
`appendIdentityToUrl`, `applyResponse`, `getLibraryInfo`, `setDebug`,
|
|
26
|
+
`setConsent`). The browser bundle uses a callable string-command dispatcher
|
|
27
|
+
so a synchronous stub can queue calls before the library finishes loading
|
|
28
|
+
asynchronously — that doesn't apply in Node, so methods are used instead for
|
|
29
|
+
discoverability.
|
|
30
|
+
|
|
31
|
+
## One instance per process, `forRequest()` per request
|
|
32
|
+
|
|
33
|
+
Call `configure()` once, when your server starts — the same way you'd
|
|
34
|
+
configure once per page load in the browser. Node has no equivalent of a
|
|
35
|
+
page load resetting everything between visitors, though, so a real server
|
|
36
|
+
needs a way to resolve _this request's_ visitor identity without recreating
|
|
37
|
+
or reconfiguring the whole instance. That's what `forRequest()` is for:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const alloy = createInstance();
|
|
41
|
+
await alloy.configure({ orgId: "...", datastreamId: "..." });
|
|
42
|
+
|
|
43
|
+
// Per request:
|
|
44
|
+
app.get("/", async (req, res) => {
|
|
45
|
+
const request = alloy.forRequest({
|
|
46
|
+
cookie: createCookieServiceForThisRequest(req, res),
|
|
47
|
+
});
|
|
48
|
+
const result = await request.sendEvent({ xdm: { eventType: "..." } });
|
|
49
|
+
// ...
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`forRequest()` returns a bound handle with the same methods (minus
|
|
54
|
+
`configure`, since it's already configured) backed by its own instance,
|
|
55
|
+
reconfigured under the hood with the same config plus your request-scoped
|
|
56
|
+
`cookie` override. Two requests that share a cookie store (i.e. the same
|
|
57
|
+
visitor, cookies read from their real `Cookie` header) resolve to the same
|
|
58
|
+
identity; requests that don't share one get independent identities — no
|
|
59
|
+
server-side session cache required, since the continuity lives in the
|
|
60
|
+
cookie itself, the same way it already does across browser page loads.
|
|
61
|
+
|
|
62
|
+
## Hybrid personalization
|
|
63
|
+
|
|
64
|
+
`createInstance()` includes a fetch-only Personalization component: pass
|
|
65
|
+
`decisionScopes` or `personalization.surfaces`/`personalization.decisionScopes`
|
|
66
|
+
to `sendEvent()` and the result includes a `propositions` array — the same
|
|
67
|
+
shape the browser SDK's `applyPropositions` command expects:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
const { propositions } = await request.sendEvent({
|
|
71
|
+
xdm: { eventType: "decisioning.propositionFetch" },
|
|
72
|
+
personalization: { surfaces: ["web://example.com/"] },
|
|
73
|
+
});
|
|
74
|
+
// Serialize `propositions` into the page, then client-side:
|
|
75
|
+
// alloy("applyPropositions", { propositions });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
There's no rendering here — no DOM actions, view cache, click tracking, or
|
|
79
|
+
display-notification batching, since there's no page to render into in
|
|
80
|
+
Node. That's the client's job once it calls `applyPropositions`; use
|
|
81
|
+
`createCustomInstance({ components: [] })` to omit this component entirely.
|
|
82
|
+
|
|
83
|
+
## Consent
|
|
84
|
+
|
|
85
|
+
`createInstance()` includes Consent, so `setConsent` is available the same
|
|
86
|
+
way it is in the browser — call it per-request, on the `forRequest()` handle,
|
|
87
|
+
with whatever consent value the visitor already recorded in their browser:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
await request.setConsent({
|
|
91
|
+
consent: [{ standard: "Adobe", version: "1.0", value: { general: "in" } }],
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Like identity, consent state is tied to the visitor's cookies (read/written
|
|
96
|
+
through `platformServices.cookie`), so it's automatically scoped correctly
|
|
97
|
+
by whatever `cookie` override you already pass to `forRequest()` — no
|
|
98
|
+
separate wiring needed. If you build a custom instance with
|
|
99
|
+
`createCustomInstance({ components: [...] })` and leave `consent` out,
|
|
100
|
+
`setConsent` will still exist as a method but will reject when called, the
|
|
101
|
+
same as calling an unregistered command in the browser bundle.
|
|
102
|
+
|
|
103
|
+
## Context
|
|
104
|
+
|
|
105
|
+
A minimal, always-on Context component attaches `implementationDetails` to
|
|
106
|
+
every event automatically. There's no DOM to read device/viewport/timezone
|
|
107
|
+
info from like the browser version does, but if you pass the real incoming
|
|
108
|
+
request to `forRequest({ request })`, Context also derives
|
|
109
|
+
`web.webPageDetails.URL` from its `Referer` header, and the visitor's real
|
|
110
|
+
headers (`User-Agent`, client hints, `X-Forwarded-For`, etc. — see
|
|
111
|
+
`pickForwardableHeaders`) get forwarded upstream to Edge Network, so its own
|
|
112
|
+
server-side device/locale/geo parsing has real data to work with instead of
|
|
113
|
+
whatever Node's own `fetch()` would send by default:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
const request = alloy.forRequest({
|
|
117
|
+
cookie: createCookieServiceForThisRequest(req, res),
|
|
118
|
+
request: req, // any { headers } object — a raw request, Express req, etc.
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Everything else the browser's Context collects (screen size, viewport,
|
|
123
|
+
local timezone) has no honest server-side source and isn't guessed at here —
|
|
124
|
+
merge real values directly via `sendEvent({ xdm: { ... } })` if you have
|
|
125
|
+
them.
|
|
126
|
+
|
|
127
|
+
## Platform services
|
|
128
|
+
|
|
129
|
+
Both `createInstance(options)` and `forRequest(overrides)` accept a
|
|
130
|
+
`platformServices`-shaped object (flat on `forRequest`, nested under
|
|
131
|
+
`platformServices` on `createInstance`) to override any of the default
|
|
132
|
+
in-memory Node services (`network`, `storage`, `cookie`, `runtime`,
|
|
133
|
+
`legacy`, `globals`) with your own implementation:
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
const alloy = createInstance({
|
|
137
|
+
platformServices: {
|
|
138
|
+
cookie: createDefaultCookieService(),
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`cookie` is the one you'll reach for most — the default is an in-memory jar,
|
|
144
|
+
so identity resets whenever it's the only thing available (e.g. on the
|
|
145
|
+
top-level instance, if you never call `forRequest()`). Overrides must
|
|
146
|
+
implement the `CookieService` interface (`get`, `getAll`, `set`, `remove`,
|
|
147
|
+
`withConverter`) documented in `@adobe/alloy-core/services`.
|
|
148
|
+
|
|
149
|
+
### What's visitor-scoped vs. process-scoped
|
|
150
|
+
|
|
151
|
+
Everything ECID/identity-related — the `kndctr_`/`AMCV_` cookies, edge
|
|
152
|
+
cluster/location-hint affinity, consent cookies forwarded to the Edge
|
|
153
|
+
Network — goes through `cookie`, so it's already correctly bucketed by
|
|
154
|
+
`forRequest()`'s per-request override. `request` (the real incoming HTTP
|
|
155
|
+
request, see [Context](#context)) is inherently per-request too, and is
|
|
156
|
+
never inherited from the top-level instance the way `cookie`/`storage`
|
|
157
|
+
aren't. `storage`'s current uses (debug-flag
|
|
158
|
+
persistence, an Assurance validation client ID) are process-level
|
|
159
|
+
bookkeeping, not visitor state, _except_ that Assurance client ID: it's
|
|
160
|
+
meant to be a stable per-visitor value, but with the default in-memory
|
|
161
|
+
`storage` it resets every request unless you also pass a `storage` override
|
|
162
|
+
to `forRequest()`, the same way you would `cookie`.
|
|
163
|
+
|
|
164
|
+
If a future component needs to persist visitor-scoped state beyond a single
|
|
165
|
+
request (e.g. decisioning event history for content-card/frequency-capping
|
|
166
|
+
dedup, à la the browser's `personalizationStorageEnabled` — not implemented
|
|
167
|
+
in Node yet), it should read/write that through `platformServices.storage`
|
|
168
|
+
and rely on the same `forRequest({ storage: ... })` override mechanism,
|
|
169
|
+
not a new one.
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adobe/alloy-node",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"description": "Adobe Experience Platform Web SDK - Node.js entrypoint",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/adobe/alloy.git",
|
|
9
|
+
"directory": "packages/node"
|
|
10
|
+
},
|
|
11
|
+
"author": "Adobe Inc.",
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./src/index.js",
|
|
23
|
+
"default": "./src/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@adobe/alloy-core": "^1.2.3-beta.2"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"typecheck": "tsc -p jsconfig.json"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// This file contains the optional components that can be included in a Node
|
|
14
|
+
// build. Mirrors the shape of packages/browser/src/allOptionalComponents.js.
|
|
15
|
+
|
|
16
|
+
export { default as personalization } from "./components/personalization.js";
|
|
17
|
+
export { consent } from "@adobe/alloy-core";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// placeholder until @adobe/alloy-core passes a typecheck.
|
|
2
|
+
declare module "@adobe/alloy-core" {
|
|
3
|
+
export function createCustomInstance(
|
|
4
|
+
options: Record<string, unknown>,
|
|
5
|
+
createPlatformServices?: () => unknown,
|
|
6
|
+
coreConfigValidators?: unknown,
|
|
7
|
+
): (commandName: string, options?: Record<string, unknown>) => Promise<any>;
|
|
8
|
+
|
|
9
|
+
export function createCoreConfigs(): unknown;
|
|
10
|
+
|
|
11
|
+
export function consent(options: Record<string, unknown>): unknown;
|
|
12
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @import { NodeRequestLike } from "../services/createNodePlatformServices.js" */
|
|
14
|
+
|
|
15
|
+
import packageJson from "../../package.json" with { type: "json" };
|
|
16
|
+
import getHeader from "../services/getHeader.js";
|
|
17
|
+
|
|
18
|
+
const LIBRARY_NAME = "https://ns.adobe.com/experience/alloy";
|
|
19
|
+
const { version: LIBRARY_VERSION } = packageJson;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Node's analog to the browser bundle's Context component. No DOM to read
|
|
23
|
+
* device/viewport/timezone from, so this only attaches `implementationDetails`
|
|
24
|
+
* and, best-effort, `web.webPageDetails.URL` from the `Referer` header of
|
|
25
|
+
* `forRequest({ request })`.
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} params
|
|
28
|
+
* @param {{ request?: NodeRequestLike }} params.platformServices
|
|
29
|
+
*/
|
|
30
|
+
const createContext = ({ platformServices }) => ({
|
|
31
|
+
lifecycle: {
|
|
32
|
+
/** @param {{ event: any }} params */
|
|
33
|
+
onBeforeEvent({ event }) {
|
|
34
|
+
event.mergeXdm({
|
|
35
|
+
implementationDetails: {
|
|
36
|
+
name: LIBRARY_NAME,
|
|
37
|
+
version: LIBRARY_VERSION,
|
|
38
|
+
environment: "server",
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const referer = getHeader(platformServices.request?.headers, "referer");
|
|
43
|
+
if (typeof referer === "string" && referer) {
|
|
44
|
+
event.mergeXdm({
|
|
45
|
+
web: { webPageDetails: { URL: referer } },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return Promise.resolve();
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
createContext.namespace = "Context";
|
|
55
|
+
|
|
56
|
+
export default createContext;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_CONTENT_ITEM,
|
|
15
|
+
DOM_ACTION,
|
|
16
|
+
HTML_CONTENT_ITEM,
|
|
17
|
+
JSON_CONTENT_ITEM,
|
|
18
|
+
REDIRECT_ITEM,
|
|
19
|
+
RULESET_ITEM,
|
|
20
|
+
MESSAGE_IN_APP,
|
|
21
|
+
MESSAGE_CONTENT_CARD,
|
|
22
|
+
} from "@adobe/alloy-core/constants/schema.js";
|
|
23
|
+
|
|
24
|
+
const DECISIONS_HANDLE = "personalization:decisions";
|
|
25
|
+
|
|
26
|
+
// Every schema `applyPropositions` (client-side) knows how to process.
|
|
27
|
+
// Unlike the browser component, there's no page-wide scope to conditionally
|
|
28
|
+
// gate DOM_ACTION on — Node has no page, so it's always requested.
|
|
29
|
+
const SCHEMAS = [
|
|
30
|
+
DEFAULT_CONTENT_ITEM,
|
|
31
|
+
HTML_CONTENT_ITEM,
|
|
32
|
+
JSON_CONTENT_ITEM,
|
|
33
|
+
REDIRECT_ITEM,
|
|
34
|
+
RULESET_ITEM,
|
|
35
|
+
MESSAGE_IN_APP,
|
|
36
|
+
MESSAGE_CONTENT_CARD,
|
|
37
|
+
DOM_ACTION,
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/** @param {Array<string>} array */
|
|
41
|
+
const dedupe = (array) =>
|
|
42
|
+
array.filter((item, pos) => array.indexOf(item) === pos);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Fetch-only personalization for Node: requests decisions for
|
|
46
|
+
* `decisionScopes`/`personalization.surfaces`/`personalization.decisionScopes`
|
|
47
|
+
* passed to `sendEvent`, and returns the raw `personalization:decisions`
|
|
48
|
+
* payloads as `propositions` — the same shape the browser SDK's
|
|
49
|
+
* `applyPropositions` command expects, so a server can hand them to a
|
|
50
|
+
* client to render.
|
|
51
|
+
*
|
|
52
|
+
* Deliberately does not include any of the browser Personalization
|
|
53
|
+
* component's rendering machinery (DOM actions, view cache, click
|
|
54
|
+
* tracking, display-notification batching, default page-wide scope) —
|
|
55
|
+
* there's no page or DOM in Node to render into or infer a scope from,
|
|
56
|
+
* and rendering-triggered notifications are the client's responsibility
|
|
57
|
+
* once it calls `applyPropositions`.
|
|
58
|
+
*/
|
|
59
|
+
const createPersonalization = () => ({
|
|
60
|
+
lifecycle: {
|
|
61
|
+
/**
|
|
62
|
+
* @param {Object} params
|
|
63
|
+
* @param {any} params.event
|
|
64
|
+
* @param {string[]} [params.decisionScopes]
|
|
65
|
+
* @param {{ decisionScopes?: string[], surfaces?: string[] }} [params.personalization]
|
|
66
|
+
* @param {(callback: (arg: { response: any }) => Object) => void} params.onResponse
|
|
67
|
+
*/
|
|
68
|
+
onBeforeEvent({
|
|
69
|
+
event,
|
|
70
|
+
decisionScopes = [],
|
|
71
|
+
personalization = {},
|
|
72
|
+
onResponse,
|
|
73
|
+
}) {
|
|
74
|
+
const scopes = dedupe([
|
|
75
|
+
...decisionScopes,
|
|
76
|
+
...(personalization.decisionScopes || []),
|
|
77
|
+
]);
|
|
78
|
+
const surfaces = dedupe(personalization.surfaces || []);
|
|
79
|
+
|
|
80
|
+
if (scopes.length === 0 && surfaces.length === 0) {
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
event.mergeQuery({
|
|
85
|
+
personalization: { schemas: SCHEMAS, decisionScopes: scopes, surfaces },
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
onResponse(({ response }) => ({
|
|
89
|
+
propositions: response.getPayloadsByType(DECISIONS_HANDLE),
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
return Promise.resolve();
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
createPersonalization.namespace = "Personalization";
|
|
98
|
+
|
|
99
|
+
export default createPersonalization;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// This file contains the components that are always active in a Node build,
|
|
14
|
+
// regardless of what's passed to `components`. Mirrors the shape of
|
|
15
|
+
// packages/browser/src/components/requiredComponentCreators.js.
|
|
16
|
+
|
|
17
|
+
export { default as context } from "./context.js";
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @import { NetworkService, StorageService, CookieService, RuntimeService, LegacyService, GlobalsService } from "@adobe/alloy-core/services" */
|
|
14
|
+
/** @import { Logger } from "@adobe/alloy-core/core/types.js" */
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
createCustomInstance as createCoreCustomInstance,
|
|
18
|
+
createCoreConfigs,
|
|
19
|
+
} from "@adobe/alloy-core";
|
|
20
|
+
import createNodePlatformServices from "./services/createNodePlatformServices.js";
|
|
21
|
+
import * as allRequiredComponents from "./components/requiredComponentCreators.js";
|
|
22
|
+
|
|
23
|
+
/** @import { NodeRequestLike } from "./services/createNodePlatformServices.js" */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} PlatformServiceOverrides
|
|
27
|
+
* @property {(logger: Logger) => NetworkService} [network]
|
|
28
|
+
* @property {StorageService} [storage]
|
|
29
|
+
* @property {CookieService} [cookie]
|
|
30
|
+
* @property {RuntimeService} [runtime]
|
|
31
|
+
* @property {LegacyService} [legacy]
|
|
32
|
+
* @property {GlobalsService} [globals]
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {(commandName: string, options?: Record<string, unknown>) => Promise<any>} ExecuteCommand
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
// Everything but `configure`: available directly on a top-level instance and
|
|
40
|
+
// on every bound handle returned by forRequest() (which is configured for
|
|
41
|
+
// you). Kept in sync with core's required components (DataCollector,
|
|
42
|
+
// Identity, LibraryInfo) — grows as Node picks up optional components.
|
|
43
|
+
const COMMAND_NAMES = [
|
|
44
|
+
"setDebug",
|
|
45
|
+
"sendEvent",
|
|
46
|
+
"applyResponse",
|
|
47
|
+
"getIdentity",
|
|
48
|
+
"appendIdentityToUrl",
|
|
49
|
+
"getLibraryInfo",
|
|
50
|
+
"setConsent",
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {ExecuteCommand} executeCommand
|
|
55
|
+
* @param {string[]} commandNames
|
|
56
|
+
* @returns {Record<string, (options?: Record<string, unknown>) => Promise<any>>}
|
|
57
|
+
*/
|
|
58
|
+
const bindCommandMethods = (executeCommand, commandNames) => {
|
|
59
|
+
/** @type {Record<string, (options?: Record<string, unknown>) => Promise<any>>} */
|
|
60
|
+
const methods = {};
|
|
61
|
+
commandNames.forEach((commandName) => {
|
|
62
|
+
methods[commandName] = (options) => executeCommand(commandName, options);
|
|
63
|
+
});
|
|
64
|
+
return methods;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Node entrypoint's method-based wrapper around core's string-command
|
|
69
|
+
* dispatcher. Unlike the browser bundle, there's no pre-load stub queue to
|
|
70
|
+
* keep call syntax uniform for, so real methods give better discoverability
|
|
71
|
+
* and typo-safety than `alloy("commandName", options)`.
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} [params]
|
|
74
|
+
* @param {string} [params.name]
|
|
75
|
+
* @param {Array<Object>} [params.monitors]
|
|
76
|
+
* @param {Array<Function>} [params.components]
|
|
77
|
+
* @param {PlatformServiceOverrides} [params.platformServices]
|
|
78
|
+
*/
|
|
79
|
+
const createNodeAlloy = ({
|
|
80
|
+
name,
|
|
81
|
+
monitors,
|
|
82
|
+
components = [],
|
|
83
|
+
platformServices = {},
|
|
84
|
+
} = {}) => {
|
|
85
|
+
// Mirrors packages/browser/src/index.js: components a consumer can't opt
|
|
86
|
+
// out of (currently just Context), prepended to whatever they passed in.
|
|
87
|
+
const allComponents = [
|
|
88
|
+
...Object.values(allRequiredComponents),
|
|
89
|
+
...components,
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const executeCommand = createCoreCustomInstance(
|
|
93
|
+
{ name, monitors, components: allComponents },
|
|
94
|
+
() => createNodePlatformServices(platformServices),
|
|
95
|
+
createCoreConfigs(),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
/** @type {Record<string, unknown> | undefined} */
|
|
99
|
+
let capturedConfig;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A handle scoped to a single request, backed by its own fresh instance
|
|
103
|
+
* reconfigured with this instance's config plus `requestOverrides`
|
|
104
|
+
* layered on top of its platformServices. Requires `configure()` to have
|
|
105
|
+
* already resolved on this instance.
|
|
106
|
+
*
|
|
107
|
+
* A fresh instance (not just a swapped `cookie`) is required because
|
|
108
|
+
* Identity caches its resolved ECID in a plain closure for the life of
|
|
109
|
+
* the instance — reusing one across requests would leak identity between
|
|
110
|
+
* visitors regardless of which cookie jar was passed in.
|
|
111
|
+
*
|
|
112
|
+
* For the same reason, `cookie`/`storage` never fall back to this
|
|
113
|
+
* instance's own platformServices; only the stateless
|
|
114
|
+
* `network`/`runtime`/`legacy`/`globals` slots do.
|
|
115
|
+
*
|
|
116
|
+
* `request` (any object with a `headers` property — a Node
|
|
117
|
+
* `IncomingMessage`, an Express `req`, a fetch `Request`, etc.) is the
|
|
118
|
+
* real incoming request this call is proxying, used to forward the
|
|
119
|
+
* visitor's real headers to Edge Network and populate
|
|
120
|
+
* `web.webPageDetails.URL` (see components/context.js).
|
|
121
|
+
*
|
|
122
|
+
* @param {PlatformServiceOverrides & { request?: NodeRequestLike }} [requestOverrides]
|
|
123
|
+
*/
|
|
124
|
+
const forRequest = (requestOverrides = {}) => {
|
|
125
|
+
if (!capturedConfig) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
"forRequest() can only be called after configure() has resolved.",
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const { request, ...requestPlatformServices } = requestOverrides;
|
|
131
|
+
const { cookie, storage, ...sharedPlatformServices } = platformServices;
|
|
132
|
+
const requestExecuteCommand = createCoreCustomInstance(
|
|
133
|
+
{ name, monitors, components: allComponents },
|
|
134
|
+
() =>
|
|
135
|
+
createNodePlatformServices({
|
|
136
|
+
...sharedPlatformServices,
|
|
137
|
+
...requestPlatformServices,
|
|
138
|
+
request,
|
|
139
|
+
}),
|
|
140
|
+
// Fresh validators per request: orgId/datastreamId are being
|
|
141
|
+
// reconfigured with the exact same values on purpose here, once per
|
|
142
|
+
// request, so they must not be checked for uniqueness against every
|
|
143
|
+
// other request this process has ever handled.
|
|
144
|
+
createCoreConfigs(),
|
|
145
|
+
);
|
|
146
|
+
requestExecuteCommand("configure", capturedConfig);
|
|
147
|
+
return bindCommandMethods(requestExecuteCommand, COMMAND_NAMES);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
...bindCommandMethods(executeCommand, COMMAND_NAMES),
|
|
152
|
+
/** @param {Record<string, unknown>} options */
|
|
153
|
+
configure(options) {
|
|
154
|
+
// Only capture the config once configure() has actually succeeded —
|
|
155
|
+
// if it rejects (invalid config), capturedConfig must stay unset, so
|
|
156
|
+
// a subsequent forRequest() call still throws instead of silently
|
|
157
|
+
// reconfiguring every request with a config core already rejected.
|
|
158
|
+
return executeCommand("configure", options).then((result) => {
|
|
159
|
+
capturedConfig = options;
|
|
160
|
+
return result;
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
forRequest,
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export default createNodeAlloy;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import createNodeAlloy from "./createNodeAlloy.js";
|
|
13
|
+
import * as allOptionalComponents from "./allOptionalComponents.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @alpha
|
|
17
|
+
*
|
|
18
|
+
* Creates a custom Node instance, including only the given optional
|
|
19
|
+
* components (e.g. `personalization`). See `createInstance` to get every
|
|
20
|
+
* optional component by default.
|
|
21
|
+
*
|
|
22
|
+
* Returns an object with real methods (`configure`, `sendEvent`,
|
|
23
|
+
* `getIdentity`, ...) rather than the browser bundle's
|
|
24
|
+
* `alloy("commandName", options)` calling convention — there's no pre-load
|
|
25
|
+
* stub queue to keep call syntax uniform for in Node, so methods give
|
|
26
|
+
* better discoverability. This is an early skeleton: most optional
|
|
27
|
+
* components (Consent, Audiences, etc.) are not wired up yet.
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} [options]
|
|
30
|
+
* @param {Array<Function>} [options.components]
|
|
31
|
+
* @param {Object} [options.platformServices] Overrides for one or more of
|
|
32
|
+
* the default Node platform services (network, storage, cookie, runtime,
|
|
33
|
+
* legacy, globals) — e.g. a `cookie` service backed by a real HTTP
|
|
34
|
+
* request/response so identity persists across requests instead of only
|
|
35
|
+
* within a single process. See `createNodePlatformServices` for the shape
|
|
36
|
+
* of each override.
|
|
37
|
+
*/
|
|
38
|
+
export const createCustomInstance = ({
|
|
39
|
+
platformServices = {},
|
|
40
|
+
components = [],
|
|
41
|
+
...options
|
|
42
|
+
} = {}) =>
|
|
43
|
+
createNodeAlloy({
|
|
44
|
+
...options,
|
|
45
|
+
components,
|
|
46
|
+
platformServices,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @alpha
|
|
51
|
+
*
|
|
52
|
+
* Creates a Node instance with every optional component included.
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} [options]
|
|
55
|
+
* @param {Object} [options.platformServices]
|
|
56
|
+
*/
|
|
57
|
+
export const createInstance = (options = {}) =>
|
|
58
|
+
createCustomInstance({
|
|
59
|
+
...options,
|
|
60
|
+
components: Object.values(allOptionalComponents),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export { allOptionalComponents as components };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @import { CookieService } from "@adobe/alloy-core/services" */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* In-memory only; there is no HTTP request/response to read or write real
|
|
17
|
+
* cookie headers against yet, so identity/consent state does not persist
|
|
18
|
+
* across process restarts or across requests in a server context.
|
|
19
|
+
*
|
|
20
|
+
* The `jar` and `converter` params are the recursion seam for `withConverter`,
|
|
21
|
+
* mirroring the browser cookie service built on js-cookie.
|
|
22
|
+
*
|
|
23
|
+
* @param {Map<string, string>} [jar]
|
|
24
|
+
* @param {{ read?: Function, write?: Function }} [converter]
|
|
25
|
+
* @returns {CookieService}
|
|
26
|
+
*/
|
|
27
|
+
const createNodeCookieService = (jar = new Map(), converter = {}) => ({
|
|
28
|
+
get: (name) => {
|
|
29
|
+
const value = jar.get(name);
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return converter.read ? converter.read(value, name) : value;
|
|
34
|
+
},
|
|
35
|
+
getAll: () => {
|
|
36
|
+
/** @type {Record<string, string>} */
|
|
37
|
+
const result = {};
|
|
38
|
+
jar.forEach((value, name) => {
|
|
39
|
+
result[name] = converter.read ? converter.read(value, name) : value;
|
|
40
|
+
});
|
|
41
|
+
return result;
|
|
42
|
+
},
|
|
43
|
+
set: (name, value) => {
|
|
44
|
+
const finalValue = converter.write ? converter.write(value, name) : value;
|
|
45
|
+
jar.set(name, finalValue);
|
|
46
|
+
return finalValue;
|
|
47
|
+
},
|
|
48
|
+
remove: (name) => {
|
|
49
|
+
jar.delete(name);
|
|
50
|
+
},
|
|
51
|
+
withConverter: (newConverter) => createNodeCookieService(jar, newConverter),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export default createNodeCookieService;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @import { GlobalsService } from "@adobe/alloy-core/services" */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* There is no page, DOM, or `<script>` queue in Node, so anything tied to
|
|
17
|
+
* those concepts falls back to an empty/static default.
|
|
18
|
+
*
|
|
19
|
+
* @returns {GlobalsService}
|
|
20
|
+
*/
|
|
21
|
+
const createNodeGlobalsService = () => ({
|
|
22
|
+
getInstanceNames: () => [],
|
|
23
|
+
getInstanceQueue: () => [],
|
|
24
|
+
getMonitors: () => [],
|
|
25
|
+
getLocationSearch: () => "",
|
|
26
|
+
getLocationHash: () => "",
|
|
27
|
+
getUserAgent: () => "",
|
|
28
|
+
getHostname: () => "",
|
|
29
|
+
getPageLocation: () => ({ host: "", pathname: "" }),
|
|
30
|
+
isPageSsl: () => true,
|
|
31
|
+
fireReferrerHideableImage: async () => {},
|
|
32
|
+
getWindowContext: () => ({
|
|
33
|
+
title: "",
|
|
34
|
+
url: "",
|
|
35
|
+
referrer: "",
|
|
36
|
+
height: 0,
|
|
37
|
+
width: 0,
|
|
38
|
+
scrollY: 0,
|
|
39
|
+
scrollX: 0,
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export default createNodeGlobalsService;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @import { LegacyService } from "@adobe/alloy-core/services" */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Visitor.js and `adobe.optIn` are browser-only legacy libraries with no
|
|
17
|
+
* Node equivalent, so this service is a no-op.
|
|
18
|
+
*
|
|
19
|
+
* @returns {LegacyService}
|
|
20
|
+
*/
|
|
21
|
+
const createNodeLegacyService = () => ({
|
|
22
|
+
getEcidFromVisitor: async () => undefined,
|
|
23
|
+
awaitVisitorOptIn: async () => {},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export default createNodeLegacyService;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @import { NetworkService } from "@adobe/alloy-core/services" */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Node has no `navigator.sendBeacon`, so there is no separate unload-safe
|
|
17
|
+
* transport — both network strategies use `fetch`.
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} [options]
|
|
20
|
+
* @param {Record<string, string>} [options.headers] Extra headers merged
|
|
21
|
+
* into every outgoing request (e.g. from `pickForwardableHeaders`). Can't
|
|
22
|
+
* override `Content-Type`.
|
|
23
|
+
* @returns {NetworkService}
|
|
24
|
+
*/
|
|
25
|
+
const createNodeNetworkService = ({ headers: forwardedHeaders = {} } = {}) => {
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} url
|
|
28
|
+
* @param {string} body
|
|
29
|
+
*/
|
|
30
|
+
const sendFetchRequest = (url, body) =>
|
|
31
|
+
fetch(url, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: {
|
|
34
|
+
...forwardedHeaders,
|
|
35
|
+
"Content-Type": "text/plain; charset=UTF-8",
|
|
36
|
+
},
|
|
37
|
+
body,
|
|
38
|
+
}).then((response) =>
|
|
39
|
+
response.text().then((responseBody) => ({
|
|
40
|
+
statusCode: response.status,
|
|
41
|
+
/** @param {string} name */
|
|
42
|
+
getHeader(name) {
|
|
43
|
+
return response.headers.get(name);
|
|
44
|
+
},
|
|
45
|
+
body: responseBody,
|
|
46
|
+
})),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
sendFetchRequest,
|
|
51
|
+
sendBeaconRequest: sendFetchRequest,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default createNodeNetworkService;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @import { NetworkService, StorageService, CookieService, RuntimeService, LegacyService, GlobalsService, PlatformServices } from "@adobe/alloy-core/services" */
|
|
14
|
+
/** @import { Logger } from "@adobe/alloy-core/core/types.js" */
|
|
15
|
+
|
|
16
|
+
import createNodeNetworkService from "./createNodeNetworkService.js";
|
|
17
|
+
import createNodeStorageService from "./createNodeStorageService.js";
|
|
18
|
+
import createNodeCookieService from "./createNodeCookieService.js";
|
|
19
|
+
import createNodeRuntimeService from "./createNodeRuntimeService.js";
|
|
20
|
+
import createNodeLegacyService from "./createNodeLegacyService.js";
|
|
21
|
+
import createNodeGlobalsService from "./createNodeGlobalsService.js";
|
|
22
|
+
import pickForwardableHeaders from "./pickForwardableHeaders.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Record<string, string | string[] | undefined> | Headers} NodeRequestHeaders
|
|
26
|
+
* Either a plain headers object (Node's `IncomingMessage.headers`,
|
|
27
|
+
* Express's `req.headers`) or a WHATWG `Headers` instance (fetch-standard
|
|
28
|
+
* `Request.headers`). See `getHeader.js`.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} NodeRequestLike
|
|
33
|
+
* @property {NodeRequestHeaders} [headers]
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Each override replaces the corresponding default in-memory Node
|
|
38
|
+
* implementation wholesale — e.g. pass a `cookie` service backed by a real
|
|
39
|
+
* HTTP request/response or a database to persist identity across requests.
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} [overrides]
|
|
42
|
+
* @param {(logger: Logger) => NetworkService} [overrides.network]
|
|
43
|
+
* @param {StorageService} [overrides.storage]
|
|
44
|
+
* @param {CookieService} [overrides.cookie]
|
|
45
|
+
* @param {RuntimeService} [overrides.runtime]
|
|
46
|
+
* @param {LegacyService} [overrides.legacy]
|
|
47
|
+
* @param {GlobalsService} [overrides.globals]
|
|
48
|
+
* @param {NodeRequestLike} [overrides.request] The real incoming request
|
|
49
|
+
* Node is proxying an event for, if any — forwarded to Edge Network via
|
|
50
|
+
* `pickForwardableHeaders` (unless `network` is overridden), and exposed
|
|
51
|
+
* as-is on the returned object for the Context component to read.
|
|
52
|
+
* @returns {PlatformServices & { request?: NodeRequestLike }}
|
|
53
|
+
*/
|
|
54
|
+
const createNodePlatformServices = ({
|
|
55
|
+
network,
|
|
56
|
+
storage,
|
|
57
|
+
cookie,
|
|
58
|
+
runtime,
|
|
59
|
+
legacy,
|
|
60
|
+
globals,
|
|
61
|
+
request,
|
|
62
|
+
} = {}) => ({
|
|
63
|
+
createNetworkService: (logger) =>
|
|
64
|
+
network
|
|
65
|
+
? network(logger)
|
|
66
|
+
: createNodeNetworkService({
|
|
67
|
+
headers: pickForwardableHeaders(request?.headers),
|
|
68
|
+
}),
|
|
69
|
+
storage: storage || createNodeStorageService(),
|
|
70
|
+
cookie: cookie || createNodeCookieService(),
|
|
71
|
+
runtime: runtime || createNodeRuntimeService(),
|
|
72
|
+
legacy: legacy || createNodeLegacyService(),
|
|
73
|
+
globals: globals || createNodeGlobalsService(),
|
|
74
|
+
request,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export default createNodePlatformServices;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @import { RuntimeService } from "@adobe/alloy-core/services" */
|
|
14
|
+
|
|
15
|
+
/** @returns {RuntimeService} */
|
|
16
|
+
const createNodeRuntimeService = () => ({
|
|
17
|
+
setTimeout: globalThis.setTimeout.bind(globalThis),
|
|
18
|
+
clearTimeout: globalThis.clearTimeout.bind(globalThis),
|
|
19
|
+
atob: globalThis.atob.bind(globalThis),
|
|
20
|
+
btoa: globalThis.btoa.bind(globalThis),
|
|
21
|
+
TextEncoder: globalThis.TextEncoder,
|
|
22
|
+
TextDecoder: globalThis.TextDecoder,
|
|
23
|
+
now: () => Date.now(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export default createNodeRuntimeService;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @import { Storage, NamespacedStorage, StorageService } from "@adobe/alloy-core/services" */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* In-memory only; nothing persists across process restarts. A future
|
|
17
|
+
* revision can back this with a file, Redis, etc.
|
|
18
|
+
*
|
|
19
|
+
* @returns {Storage}
|
|
20
|
+
*/
|
|
21
|
+
const createMemoryStorage = () => {
|
|
22
|
+
const store = new Map();
|
|
23
|
+
return {
|
|
24
|
+
getItem(name) {
|
|
25
|
+
return Promise.resolve(store.has(name) ? store.get(name) : null);
|
|
26
|
+
},
|
|
27
|
+
setItem(name, value) {
|
|
28
|
+
store.set(name, value);
|
|
29
|
+
return Promise.resolve(true);
|
|
30
|
+
},
|
|
31
|
+
removeItem(name) {
|
|
32
|
+
store.delete(name);
|
|
33
|
+
return Promise.resolve(true);
|
|
34
|
+
},
|
|
35
|
+
clear() {
|
|
36
|
+
store.clear();
|
|
37
|
+
return Promise.resolve(true);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** @returns {StorageService} */
|
|
43
|
+
const createNodeStorageService = () => ({
|
|
44
|
+
createNamespacedStorage() {
|
|
45
|
+
/** @type {NamespacedStorage} */
|
|
46
|
+
return {
|
|
47
|
+
session: createMemoryStorage(),
|
|
48
|
+
persistent: createMemoryStorage(),
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export default createNodeStorageService;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @import { NodeRequestHeaders } from "./createNodePlatformServices.js" */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Reads a single header from either shape `request.headers` can be — a
|
|
17
|
+
* plain object, or a WHATWG `Headers` instance (e.g. fetch-standard
|
|
18
|
+
* `Request`s, as used by Next.js Route Handlers, Hono, etc.), which only
|
|
19
|
+
* exposes values through `.get()`.
|
|
20
|
+
*
|
|
21
|
+
* @param {NodeRequestHeaders | undefined} headers
|
|
22
|
+
* @param {string} name Lowercase header name.
|
|
23
|
+
* @returns {string | string[] | undefined}
|
|
24
|
+
*/
|
|
25
|
+
const getHeader = (headers, name) => {
|
|
26
|
+
if (!headers) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
if (headers instanceof Headers) {
|
|
30
|
+
return headers.get(name) ?? undefined;
|
|
31
|
+
}
|
|
32
|
+
return headers[name];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default getHeader;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { default as createNodeNetworkService } from "./createNodeNetworkService.js";
|
|
14
|
+
export { default as createNodeStorageService } from "./createNodeStorageService.js";
|
|
15
|
+
export { default as createNodeCookieService } from "./createNodeCookieService.js";
|
|
16
|
+
export { default as createNodeRuntimeService } from "./createNodeRuntimeService.js";
|
|
17
|
+
export { default as createNodeLegacyService } from "./createNodeLegacyService.js";
|
|
18
|
+
export { default as createNodeGlobalsService } from "./createNodeGlobalsService.js";
|
|
19
|
+
export { default as createNodePlatformServices } from "./createNodePlatformServices.js";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @import { NodeRequestHeaders } from "./createNodePlatformServices.js" */
|
|
14
|
+
|
|
15
|
+
import getHeader from "./getHeader.js";
|
|
16
|
+
|
|
17
|
+
// Forwarded so Edge Network's own device/locale/geo parsing sees the real
|
|
18
|
+
// visitor instead of this Node process.
|
|
19
|
+
const FORWARDABLE_HEADER_NAMES = [
|
|
20
|
+
"user-agent",
|
|
21
|
+
"accept-language",
|
|
22
|
+
// User-Agent Client Hints
|
|
23
|
+
"sec-ch-ua",
|
|
24
|
+
"sec-ch-ua-mobile",
|
|
25
|
+
"sec-ch-ua-platform",
|
|
26
|
+
"sec-ch-ua-platform-version",
|
|
27
|
+
"sec-ch-ua-arch",
|
|
28
|
+
"sec-ch-ua-bitness",
|
|
29
|
+
"sec-ch-ua-full-version-list",
|
|
30
|
+
"sec-ch-ua-model",
|
|
31
|
+
"sec-ch-ua-wow64",
|
|
32
|
+
// Client IP, as set by a proxy/load balancer in front of this server —
|
|
33
|
+
// otherwise Edge Network attributes every request to this server's IP.
|
|
34
|
+
"x-forwarded-for",
|
|
35
|
+
"forwarded",
|
|
36
|
+
"x-real-ip",
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {NodeRequestHeaders} [headers]
|
|
41
|
+
* @returns {Record<string, string>}
|
|
42
|
+
*/
|
|
43
|
+
const pickForwardableHeaders = (headers) => {
|
|
44
|
+
/** @type {Record<string, string>} */
|
|
45
|
+
const picked = {};
|
|
46
|
+
FORWARDABLE_HEADER_NAMES.forEach((name) => {
|
|
47
|
+
const value = getHeader(headers, name);
|
|
48
|
+
if (typeof value === "string" && value) {
|
|
49
|
+
picked[name] = value;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return picked;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default pickForwardableHeaders;
|