@klarna/add-to-klarna 2.0.0 → 2.0.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/README.md +16 -13
- package/dist/index.cjs +16 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ Drop the library straight into an HTML page via an ESM-aware CDN — no
|
|
|
79
79
|
|
|
80
80
|
```html
|
|
81
81
|
<script type="module">
|
|
82
|
-
import { createAddToKlarnaClient } from "https://esm.sh/@klarna/add-to-klarna@2.0.
|
|
82
|
+
import { createAddToKlarnaClient } from "https://esm.sh/@klarna/add-to-klarna@2.0.1";
|
|
83
83
|
|
|
84
84
|
const klarna = createAddToKlarnaClient({ region: "eu" });
|
|
85
85
|
|
|
@@ -92,8 +92,8 @@ Drop the library straight into an HTML page via an ESM-aware CDN — no
|
|
|
92
92
|
</script>
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
`esm.sh` (above), `https://cdn.jsdelivr.net/npm/@klarna/add-to-klarna@2.0.
|
|
96
|
-
and `https://unpkg.com/@klarna/add-to-klarna@2.0.
|
|
95
|
+
`esm.sh` (above), `https://cdn.jsdelivr.net/npm/@klarna/add-to-klarna@2.0.1/+esm`,
|
|
96
|
+
and `https://unpkg.com/@klarna/add-to-klarna@2.0.1?module` all serve the
|
|
97
97
|
ESM build and resolve the `jose` dependency transparently. Always pin a
|
|
98
98
|
version in production so a future release can't change behaviour under
|
|
99
99
|
your page.
|
|
@@ -132,22 +132,25 @@ The composed URL has the shape:
|
|
|
132
132
|
```
|
|
133
133
|
<oneLinkBase>?pid=WebApp
|
|
134
134
|
&c=add-to-klarna
|
|
135
|
+
&af_dp=<url-encoded scheme + /loyalty-cards-v2/add-to-klarna/<brandNickname>/<encryptedPayload>>
|
|
135
136
|
&deep_link_value=<url-encoded /loyalty-cards-v2/add-to-klarna/<brandNickname>/<encryptedPayload>>
|
|
136
137
|
&af_web_dp=<desktop fallback URL>
|
|
137
138
|
```
|
|
138
139
|
|
|
139
|
-
|
|
140
|
+
`deep_link_value` is the in-app path AppsFlyer reads after the app is open. `af_dp` is the address Android opens when the https OneLink is not claimed as an App Link. It is the same path on the build's custom scheme (`klarnaoneoff://loyalty-cards-v2/add-to-klarna/...`), because a bare `klarnaoneoff://` launches the app and leaves it on the home screen. The app inserts a third slash before it matches the route.
|
|
140
141
|
|
|
141
|
-
|
|
142
|
-
| -------------- | -------------------------------------------- |
|
|
143
|
-
| `pink` | `https://l.klarna.com/22XC` |
|
|
144
|
-
| `internalpink` | `https://klarnainternalpink.onelink.me/lXgD` |
|
|
145
|
-
| `yellow` | `https://klarnayellow.onelink.me/JQ8X` |
|
|
146
|
-
| `staging` | `https://klarnastaging.onelink.me/hV1K` |
|
|
147
|
-
| `oneoff` | `https://klarnaoneoff.onelink.me/FaEr` |
|
|
148
|
-
| `local` | `https://klarnalocal.onelink.me/dxUs` |
|
|
142
|
+
The `<oneLinkBase>` and `af_dp` scheme are fixed per `clientTarget`:
|
|
149
143
|
|
|
150
|
-
|
|
144
|
+
| `clientTarget` | OneLink base URL | `af_dp` scheme |
|
|
145
|
+
| -------------- | -------------------------------------------- | ----------------------- |
|
|
146
|
+
| `pink` | `https://l.klarna.com/22XC` | `klarna://` |
|
|
147
|
+
| `internalpink` | `https://klarnainternalpink.onelink.me/lXgD` | `klarnainternalpink://` |
|
|
148
|
+
| `yellow` | `https://klarnayellow.onelink.me/JQ8X` | `klarnayellow://` |
|
|
149
|
+
| `staging` | `https://klarnastaging.onelink.me/hV1K` | `klarnastaging://` |
|
|
150
|
+
| `oneoff` | `https://klarnaoneoff.onelink.me/FaEr` | `klarnaoneoff://` |
|
|
151
|
+
| `local` | `https://klarnalocal.onelink.me/dxUs` | `klarnadev://` |
|
|
152
|
+
|
|
153
|
+
The library sets `af_dp` (the screen on the app's custom scheme) and `af_web_dp` (the desktop fallback) on the URL itself.
|
|
151
154
|
The App Store / Play Store fallbacks for mobile are configured on the
|
|
152
155
|
AppsFlyer OneLink template, not on the URL. Desktop fallback is always
|
|
153
156
|
`https://klarna.com/add-to-klarna` — the same production page for every
|
package/dist/index.cjs
CHANGED
|
@@ -76,6 +76,14 @@ var ONELINK_BASE_URLS = {
|
|
|
76
76
|
oneoff: "https://klarnaoneoff.onelink.me/FaEr",
|
|
77
77
|
local: "https://klarnalocal.onelink.me/dxUs"
|
|
78
78
|
};
|
|
79
|
+
var APP_URI_SCHEMES = {
|
|
80
|
+
pink: "klarna://",
|
|
81
|
+
internalpink: "klarnainternalpink://",
|
|
82
|
+
yellow: "klarnayellow://",
|
|
83
|
+
staging: "klarnastaging://",
|
|
84
|
+
oneoff: "klarnaoneoff://",
|
|
85
|
+
local: "klarnadev://"
|
|
86
|
+
};
|
|
79
87
|
var DESKTOP_FALLBACK_URL = "https://klarna.com/add-to-klarna";
|
|
80
88
|
var MEDIA_SOURCE = "WebApp";
|
|
81
89
|
var CAMPAIGN_NAME = "add-to-klarna";
|
|
@@ -107,6 +115,7 @@ function resolveConfig(options) {
|
|
|
107
115
|
jwksUrl: JWKS_URLS[environment],
|
|
108
116
|
linkPath: LINK_PATHS[environment],
|
|
109
117
|
oneLinkBaseUrl: ONELINK_BASE_URLS[clientTarget],
|
|
118
|
+
appUriScheme: APP_URI_SCHEMES[clientTarget],
|
|
110
119
|
desktopFallbackUrl: DESKTOP_FALLBACK_URL,
|
|
111
120
|
mediaSource: MEDIA_SOURCE,
|
|
112
121
|
campaignName: CAMPAIGN_NAME,
|
|
@@ -200,17 +209,23 @@ function buildOneLinkUrl(input) {
|
|
|
200
209
|
requireNonEmptyString("oneLinkBaseUrl", input.oneLinkBaseUrl, "INVALID_CONFIG");
|
|
201
210
|
requireNonEmptyString("pid", input.pid, "INVALID_CONFIG");
|
|
202
211
|
requireNonEmptyString("c", input.c, "INVALID_CONFIG");
|
|
212
|
+
requireNonEmptyString("appUriScheme", input.appUriScheme, "INVALID_CONFIG");
|
|
203
213
|
requireNonEmptyString("deepLinkValue", input.deepLinkValue, "INVALID_INPUT");
|
|
204
214
|
requireHttpUrl("webFallbackUrl", input.webFallbackUrl);
|
|
205
215
|
const params = [
|
|
206
216
|
["pid", input.pid],
|
|
207
217
|
["c", input.c],
|
|
218
|
+
["af_dp", buildAppDeepLink(input.appUriScheme, input.deepLinkValue)],
|
|
208
219
|
["deep_link_value", input.deepLinkValue],
|
|
209
220
|
["af_web_dp", input.webFallbackUrl]
|
|
210
221
|
];
|
|
211
222
|
const query = params.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
|
|
212
223
|
return `${input.oneLinkBaseUrl}?${query}`;
|
|
213
224
|
}
|
|
225
|
+
function buildAppDeepLink(appUriScheme, deepLinkValue) {
|
|
226
|
+
const path = deepLinkValue.startsWith("/") ? deepLinkValue.slice(1) : deepLinkValue;
|
|
227
|
+
return `${appUriScheme}${path}`;
|
|
228
|
+
}
|
|
214
229
|
function requireNonEmptyString(name, value, code) {
|
|
215
230
|
if (typeof value !== "string" || value.length === 0) {
|
|
216
231
|
throw new AddToKlarnaError(code, `\`${name}\` must be a non-empty string.`);
|
|
@@ -251,6 +266,7 @@ function createAddToKlarnaClient(options) {
|
|
|
251
266
|
oneLinkBaseUrl: config.oneLinkBaseUrl,
|
|
252
267
|
pid: config.mediaSource,
|
|
253
268
|
c: config.campaignName,
|
|
269
|
+
appUriScheme: config.appUriScheme,
|
|
254
270
|
deepLinkValue,
|
|
255
271
|
webFallbackUrl: config.desktopFallbackUrl
|
|
256
272
|
});
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/utils.ts","../src/buildLinkPath.ts","../src/buildPayload.ts","../src/config.ts","../src/encryptJwe.ts","../src/jwksClient.ts","../src/oneLink.ts","../src/client.ts"],"names":["importJWK","EncryptJWT"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Stable error codes. Merchant integrations should branch on these, not on\n * the human-readable message.\n */\nexport type ErrorCode =\n | \"INVALID_INPUT\"\n | \"INVALID_CONFIG\"\n | \"JWKS_FETCH_FAILED\"\n | \"JWKS_INVALID\"\n | \"NO_MATCHING_KEY\"\n | \"ENCRYPTION_FAILED\"\n | \"NAVIGATION_UNAVAILABLE\";\n\n/**\n * The single error type thrown by every public API in this package. Always\n * carries a stable {@link ErrorCode} on `code` and, where relevant, the\n * underlying error on `cause`.\n */\nexport class AddToKlarnaError extends Error {\n public readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"AddToKlarnaError\";\n this.code = code;\n // Restore prototype chain for ES5-targeted bundles. No-op on modern targets.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport function isAddToKlarnaError(err: unknown): err is AddToKlarnaError {\n return err instanceof AddToKlarnaError;\n}\n","/**\n * Encode a UTF-8 string as base64url, without padding. Works in both Node\n * and browsers.\n *\n * Used to wrap the JWE compact serialization into a single URL path segment\n * (without the wrap, the dots inside the JWE break path matching on Klarna's\n * web router).\n */\nexport function toBase64Url(input: string): string {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(input, \"utf8\").toString(\"base64url\");\n }\n const bytes = new TextEncoder().encode(input);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/**\n * Decode a base64url string back to UTF-8. Inverse of {@link toBase64Url}.\n * Exported mostly so tests can assert on the round-trip.\n */\nexport function fromBase64Url(input: string): string {\n const padded =\n input.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"==\".slice(0, (4 - (input.length % 4)) % 4);\n\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(padded, \"base64\").toString(\"utf8\");\n }\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder().decode(bytes);\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport { toBase64Url } from \"./utils.js\";\n\n/**\n * Compose the deep-link *path* that goes inside the outer AppsFlyer OneLink's\n * `deep_link_value` parameter:\n *\n * <linkPath>/<brandNickname>/<encryptedSegment>\n *\n * `linkPath` is a rooted path like `/loyalty-cards-v2/add-to-klarna` (see\n * {@link resolveConfig}). `encryptedSegment` is the JWE compact serialization\n * wrapped in a single base64url — the wrap is required because the dots in the\n * JWE compact form would otherwise break Klarna's in-app router path matching.\n *\n * Returns a path (not a full URL): the composed value is meant to be passed\n * verbatim to {@link buildOneLinkUrl} as `deepLinkValue`, which handles the\n * outer URL encoding.\n */\nexport function buildLinkPath(linkPath: string, brandNickname: string, jweCompact: string): string {\n if (typeof brandNickname !== \"string\" || brandNickname.length === 0) {\n throw new AddToKlarnaError(\"INVALID_INPUT\", \"`brandNickname` must be a non-empty string.\");\n }\n if (/[\\s/?#]/.test(brandNickname)) {\n throw new AddToKlarnaError(\n \"INVALID_INPUT\",\n `\\`brandNickname\\` contains invalid characters (whitespace, \"/\", \"?\" or \"#\"): ${brandNickname}`,\n );\n }\n if (typeof jweCompact !== \"string\" || jweCompact.length === 0) {\n throw new AddToKlarnaError(\"ENCRYPTION_FAILED\", \"Empty JWE returned from encryptor.\");\n }\n const encryptedPayload = toBase64Url(jweCompact);\n return `${linkPath}/${encodeURIComponent(brandNickname)}/${encryptedPayload}`;\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport type { DecryptedPayload } from \"./types.js\";\n\n/**\n * Build the plaintext payload that goes inside the JWE.\n *\n * The schema is `{ inputId, linkId, iat }`. Klarna's backend validates this\n * shape on decryption, so the field names and types must remain stable.\n */\nexport function buildPayload(inputId: string): DecryptedPayload {\n if (typeof inputId !== \"string\" || inputId.length === 0) {\n throw new AddToKlarnaError(\"INVALID_INPUT\", \"`inputId` must be a non-empty string.\");\n }\n return {\n inputId,\n linkId: globalThis.crypto.randomUUID(),\n iat: Math.floor(Date.now() / 1000),\n };\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport type { ClientOptions, ClientTarget, Environment, Region } from \"./types.js\";\n\n/**\n * Resolved configuration used internally. Everything has been collapsed down\n * to concrete strings by the time we construct one.\n */\nexport interface ResolvedConfig {\n jwksUrl: string;\n /**\n * The in-app deep-link path prefix. Combined with the brand nickname and\n * encrypted payload by {@link buildLinkPath}, then passed as\n * `deep_link_value` on the outer AppsFlyer OneLink.\n */\n linkPath: string;\n /**\n * AppsFlyer OneLink prefix that wraps the encrypted deep link. See\n * {@link buildOneLinkUrl}.\n */\n oneLinkBaseUrl: string;\n /**\n * Desktop fallback URL (`af_web_dp`). Always the production add-to-klarna\n * page — not selected by `environment` or `clientTarget`.\n */\n desktopFallbackUrl: string;\n /** AppsFlyer media source (`pid`). */\n mediaSource: string;\n /** AppsFlyer campaign name (`c`). */\n campaignName: string;\n region: Region;\n}\n\n/**\n * JWKS endpoints per environment. **Independent of region.** Every published\n * JWKS file contains keys for every region, namespaced by a `kid-{region}-`\n * prefix; the picker selects the right one.\n */\nconst JWKS_URLS: Record<Environment, string> = {\n production: \"https://app.klarna.com/.well-known/jwks.json\",\n staging:\n \"https://eu-non-production-klarna-static-assets.s3.eu-west-1.amazonaws.com/klapp/.well-known/jwks.json\",\n};\n\n/**\n * In-app deep-link path prefixes. Combined with `<brandNickname>/<encrypted>`\n * to form the value of `deep_link_value` on the outer AppsFlyer OneLink. The\n * Klarna app parses this path and routes to the add-to-klarna screen.\n */\nconst LINK_PATHS: Record<Environment, string> = {\n production: \"/loyalty-cards-v2/add-to-klarna\",\n staging: \"/loyalty-cards-v2/add-to-klarna\",\n};\n\n/**\n * AppsFlyer OneLink prefixes. Production uses Klarna's custom `l.klarna.com`\n * short domain; staging uses the standard AppsFlyer `*.onelink.me` host that\n * targets the staging app build.\n */\nconst ONELINK_BASE_URLS: Record<ClientTarget, string> = {\n pink: \"https://l.klarna.com/22XC\",\n internalpink: \"https://klarnainternalpink.onelink.me/lXgD\",\n yellow: \"https://klarnayellow.onelink.me/JQ8X\",\n staging: \"https://klarnastaging.onelink.me/hV1K\",\n oneoff: \"https://klarnaoneoff.onelink.me/FaEr\",\n local: \"https://klarnalocal.onelink.me/dxUs\",\n};\n\n/**\n * Desktop fallback (`af_web_dp`). One production page for every environment\n * and client target — there is no staging-specific fallback.\n */\nconst DESKTOP_FALLBACK_URL = \"https://klarna.com/add-to-klarna\";\n\n/**\n * AppsFlyer `pid` (media source).\n */\nconst MEDIA_SOURCE = \"WebApp\";\n\n/**\n * AppsFlyer `c` (campaign name).\n */\nconst CAMPAIGN_NAME = \"add-to-klarna\";\n\n/**\n * Resolve user-facing client options into a concrete config.\n *\n * Validates inputs up-front so any misconfiguration surfaces as an\n * `INVALID_CONFIG` `AddToKlarnaError` *before* the merchant page issues any\n * network calls.\n */\nexport function resolveConfig(options: ClientOptions): ResolvedConfig {\n const {\n environment = \"production\",\n clientTarget = \"pink\",\n region,\n } = options ?? ({} as ClientOptions);\n\n if (environment !== \"production\" && environment !== \"staging\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown environment: ${String(environment)}. Expected \"production\" or \"staging\".`,\n );\n }\n if (region !== \"eu\" && region !== \"us\" && region !== \"ap\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown region: ${String(region)}. Expected \"eu\", \"us\" or \"ap\".`,\n );\n }\n if (\n clientTarget !== \"pink\" &&\n clientTarget !== \"internalpink\" &&\n clientTarget !== \"yellow\" &&\n clientTarget !== \"staging\" &&\n clientTarget !== \"oneoff\" &&\n clientTarget !== \"local\"\n ) {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown clientTarget: ${String(clientTarget)}. Expected \"pink\", \"internalpink\", \"yellow\", \"staging\", \"oneoff\" or \"local\"`,\n );\n }\n\n return {\n jwksUrl: JWKS_URLS[environment],\n linkPath: LINK_PATHS[environment],\n oneLinkBaseUrl: ONELINK_BASE_URLS[clientTarget],\n desktopFallbackUrl: DESKTOP_FALLBACK_URL,\n mediaSource: MEDIA_SOURCE,\n campaignName: CAMPAIGN_NAME,\n region,\n };\n}\n","import { EncryptJWT, importJWK, type JWK } from \"jose\";\n\nimport { AddToKlarnaError } from \"./errors.js\";\nimport type { DecryptedPayload } from \"./types.js\";\n\n/**\n * The JWE profile this library produces. Pinned end-to-end:\n *\n * - `ALG` (key-encryption algorithm)\n * - `ENC` (content-encryption algorithm)\n *\n * `pickEncryptionKey` enforces the matching `kty`/`crv` on the JWKS side,\n * so a JWKS that drifts off this profile fails closed with `NO_MATCHING_KEY`\n * instead of silently producing a non-decryptable link. Changing either\n * constant is an SDK-coordinated event with Klarna's backend.\n */\nexport const ALG = \"ECDH-ES+A256KW\";\nexport const ENC = \"A256GCM\";\n\n/**\n * Encrypt the plaintext payload as a JWE using the given JWK. Produces the\n * compact serialization (five dot-separated base64url segments).\n */\nexport async function encryptJwe(\n payload: DecryptedPayload,\n key: JWK & { kid: string },\n): Promise<string> {\n let imported: Awaited<ReturnType<typeof importJWK>>;\n try {\n imported = await importJWK(key, ALG);\n } catch (cause) {\n throw new AddToKlarnaError(\n \"ENCRYPTION_FAILED\",\n `Could not import JWK with kid=\"${key.kid}\" for alg=\"${ALG}\".`,\n { cause },\n );\n }\n\n try {\n return await new EncryptJWT({\n inputId: payload.inputId,\n linkId: payload.linkId,\n })\n .setProtectedHeader({\n alg: ALG,\n enc: ENC,\n kid: key.kid,\n typ: \"JWT\",\n cty: \"application/json\",\n })\n .setIssuedAt(payload.iat)\n .encrypt(imported);\n } catch (cause) {\n throw new AddToKlarnaError(\"ENCRYPTION_FAILED\", `JWE encryption failed for kid=\"${key.kid}\".`, {\n cause,\n });\n }\n}\n","import type { JWK } from \"jose\";\n\nimport { ALG } from \"./encryptJwe.js\";\nimport { AddToKlarnaError } from \"./errors.js\";\nimport type { Region } from \"./types.js\";\n\n/**\n * The minimal JWKS shape we depend on. The actual file may contain extra\n * fields per RFC 7517; we ignore everything we do not need.\n */\nexport interface Jwks {\n keys: Array<JWK & { kid?: string; alg?: string; use?: string }>;\n}\n\n/**\n * The single key shape this library accepts off the JWKS. Pinned to the\n * tuple Klarna's backend publishes today:\n *\n * alg = ECDH-ES+A256KW\n * kty = EC\n * crv = P-256\n *\n * Anything else is rejected with `NO_MATCHING_KEY`, so a JWKS that ever\n * drifts off this profile — whether by mistake or because someone\n * managed to substitute it past TLS — fails closed instead of\n * silently using a weaker or symmetric algorithm. Any change to this\n * tuple is an SDK-coordinated event with the backend.\n */\nconst REQUIRED_KTY = \"EC\";\nconst REQUIRED_CRV = \"P-256\";\n\n/**\n * Fetch and parse the JWKS at `url`. There is no in-memory cache — caching is\n * delegated to the HTTP layer and Klarna's CDN. Wraps every failure mode in\n * a typed `AddToKlarnaError`.\n */\nexport async function fetchJwks(url: string): Promise<Jwks> {\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n });\n } catch (cause) {\n throw new AddToKlarnaError(\"JWKS_FETCH_FAILED\", `Failed to fetch JWKS from ${url}`, { cause });\n }\n\n if (!response.ok) {\n throw new AddToKlarnaError(\n \"JWKS_FETCH_FAILED\",\n `JWKS fetch returned HTTP ${response.status} ${response.statusText} for ${url}`,\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch (cause) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS response body was not valid JSON.\", { cause });\n }\n\n return assertJwks(body);\n}\n\n/**\n * Pick the encryption key for `region` from a JWKS document.\n *\n * A key is usable iff it declares all of:\n *\n * - `use === \"enc\"`\n * - `kid` starting with `kid-{region}-`\n * - `alg === ALG`, `kty === \"EC\"`, `crv === \"P-256\"` (see file header)\n *\n * If multiple keys match, the first one in document order wins — the JWKS\n * is published with the most recent key first. Throws `NO_MATCHING_KEY`\n * when nothing matches.\n */\nexport function pickEncryptionKey(jwks: Jwks, region: Region): JWK & { kid: string } {\n const prefix = `kid-${region}-`;\n const chosen = jwks.keys.find(\n (k) =>\n k.use === \"enc\" &&\n typeof k.kid === \"string\" &&\n k.kid.startsWith(prefix) &&\n k.alg === ALG &&\n k.kty === REQUIRED_KTY &&\n k.crv === REQUIRED_CRV,\n );\n\n if (!chosen || typeof chosen.kid !== \"string\") {\n throw new AddToKlarnaError(\n \"NO_MATCHING_KEY\",\n `JWKS contains no usable encryption key with kid prefix \"${prefix}\" ` +\n `(need use=\"enc\", alg=\"${ALG}\", kty=\"${REQUIRED_KTY}\", crv=\"${REQUIRED_CRV}\").`,\n );\n }\n return chosen as JWK & { kid: string };\n}\n\nfunction assertJwks(body: unknown): Jwks {\n if (!body || typeof body !== \"object\" || !(\"keys\" in body)) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS response is missing a `keys` array.\");\n }\n const keys = (body as { keys: unknown }).keys;\n if (!Array.isArray(keys)) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS `keys` is not an array.\");\n }\n return { keys: keys as Jwks[\"keys\"] };\n}\n","import { AddToKlarnaError } from \"./errors.js\";\n\n/**\n * Inputs for {@link buildOneLinkUrl}.\n *\n * All fields are required — this function is deliberately dumb: it takes\n * concrete URLs and composes them into an AppsFlyer OneLink. Environment\n * defaults live in {@link resolveConfig}.\n */\nexport interface BuildOneLinkInput {\n /** The AppsFlyer OneLink prefix (e.g. `https://l.klarna.com/22XC`). */\n oneLinkBaseUrl: string;\n\n /** AppsFlyer media source. Sent as `pid`. */\n pid: string;\n\n /** AppsFlyer campaign name. Sent as `c`. */\n c: string;\n\n /**\n * The in-app deep link path. Sent as `deep_link_value`. When the Klarna app\n * is installed on the device, AppsFlyer hands this string to the app for\n * in-app routing; when the app is not installed, AppsFlyer stores it and\n * replays it after install (deferred deep linking).\n */\n deepLinkValue: string;\n\n /** Desktop fallback URL. Sent as `af_web_dp`. */\n webFallbackUrl: string;\n}\n\n/**\n * Compose an AppsFlyer OneLink URL that:\n *\n * - opens the Klarna app to the given `deepLinkValue` when the app is\n * installed on iOS or Android,\n * - falls back to the App Store / Play Store (configured on the OneLink\n * template, not on this URL) when the app is not installed on mobile,\n * - falls back to `webFallbackUrl` on desktop.\n *\n * The returned URL has the shape:\n *\n * <oneLinkBaseUrl>?pid=<pid>&c=<c>&deep_link_value=<url-encoded deepLinkValue>\n * &af_web_dp=<url-encoded webFallbackUrl>\n *\n * Throws `AddToKlarnaError` with `INVALID_CONFIG` if any URL is missing or\n * malformed, or `INVALID_INPUT` if `deepLinkValue` is empty.\n */\nexport function buildOneLinkUrl(input: BuildOneLinkInput): string {\n requireNonEmptyString(\"oneLinkBaseUrl\", input.oneLinkBaseUrl, \"INVALID_CONFIG\");\n requireNonEmptyString(\"pid\", input.pid, \"INVALID_CONFIG\");\n requireNonEmptyString(\"c\", input.c, \"INVALID_CONFIG\");\n requireNonEmptyString(\"deepLinkValue\", input.deepLinkValue, \"INVALID_INPUT\");\n requireHttpUrl(\"webFallbackUrl\", input.webFallbackUrl);\n\n // Insertion order is preserved by URLSearchParams / Object.entries, and it\n // matters here only for output stability (easier to snapshot in tests).\n const params: Array<[string, string]> = [\n [\"pid\", input.pid],\n [\"c\", input.c],\n [\"deep_link_value\", input.deepLinkValue],\n [\"af_web_dp\", input.webFallbackUrl],\n ];\n\n const query = params.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(\"&\");\n\n return `${input.oneLinkBaseUrl}?${query}`;\n}\n\nfunction requireNonEmptyString(\n name: string,\n value: unknown,\n code: \"INVALID_CONFIG\" | \"INVALID_INPUT\",\n): void {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new AddToKlarnaError(code, `\\`${name}\\` must be a non-empty string.`);\n }\n}\n\nfunction requireHttpUrl(name: string, value: unknown): void {\n requireNonEmptyString(name, value, \"INVALID_CONFIG\");\n let url: URL;\n try {\n url = new URL(value as string);\n } catch {\n throw new AddToKlarnaError(\"INVALID_CONFIG\", `\\`${name}\\` is not a valid URL: ${value}`);\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `\\`${name}\\` must use http or https. Got: ${value}`,\n );\n }\n}\n","import { buildLinkPath } from \"./buildLinkPath.js\";\nimport { buildPayload } from \"./buildPayload.js\";\nimport { resolveConfig } from \"./config.js\";\nimport { encryptJwe } from \"./encryptJwe.js\";\nimport { AddToKlarnaError } from \"./errors.js\";\nimport { fetchJwks, pickEncryptionKey } from \"./jwksClient.js\";\nimport { buildOneLinkUrl } from \"./oneLink.js\";\nimport type { AddToKlarnaClient, BuildLinkInput, ClientOptions } from \"./types.js\";\n\n/**\n * Create an Add-to-Klarna client.\n *\n * Construction is cheap and synchronous: it validates options and wires up\n * the pipeline. The first network call happens on the first\n * {@link AddToKlarnaClient.buildLink} / {@link AddToKlarnaClient.redirect}.\n *\n * Each `buildLink` returns an AppsFlyer OneLink that:\n *\n * - opens the Klarna app to the encrypted add-to-klarna deep link when the\n * app is installed,\n * - falls back to the App Store / Play Store on mobile when the app is not\n * installed (deferred deep link replays after install),\n * - falls back to `https://klarna.com/add-to-klarna` on desktop.\n *\n * Every `buildLink` re-fetches the JWKS — caching is handled by the HTTP\n * layer and Klarna's CDN, not in memory.\n */\nexport function createAddToKlarnaClient(options: ClientOptions): AddToKlarnaClient {\n const config = resolveConfig(options);\n\n async function buildLink(input: BuildLinkInput): Promise<string> {\n if (!input || typeof input !== \"object\") {\n throw new AddToKlarnaError(\n \"INVALID_INPUT\",\n \"buildLink() requires an object with `brandNickname` and `inputId`.\",\n );\n }\n const payload = buildPayload(input.inputId);\n const jwks = await fetchJwks(config.jwksUrl);\n const key = pickEncryptionKey(jwks, config.region);\n const jwe = await encryptJwe(payload, key);\n const deepLinkValue = buildLinkPath(config.linkPath, input.brandNickname, jwe);\n return buildOneLinkUrl({\n oneLinkBaseUrl: config.oneLinkBaseUrl,\n pid: config.mediaSource,\n c: config.campaignName,\n deepLinkValue,\n webFallbackUrl: config.desktopFallbackUrl,\n });\n }\n\n async function redirect(input: BuildLinkInput): Promise<void> {\n const url = await buildLink(input);\n const w = (globalThis as { window?: { location?: { assign?: (u: string) => void } } }).window;\n const assign = w?.location?.assign;\n if (typeof assign !== \"function\") {\n throw new AddToKlarnaError(\n \"NAVIGATION_UNAVAILABLE\",\n \"redirect() requires a browser context with `window.location.assign`. Use buildLink() and navigate yourself in non-browser environments.\",\n );\n }\n assign.call(w!.location, url);\n }\n\n return { buildLink, redirect };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/utils.ts","../src/buildLinkPath.ts","../src/buildPayload.ts","../src/config.ts","../src/encryptJwe.ts","../src/jwksClient.ts","../src/oneLink.ts","../src/client.ts"],"names":["importJWK","EncryptJWT"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Stable error codes. Merchant integrations should branch on these, not on\n * the human-readable message.\n */\nexport type ErrorCode =\n | \"INVALID_INPUT\"\n | \"INVALID_CONFIG\"\n | \"JWKS_FETCH_FAILED\"\n | \"JWKS_INVALID\"\n | \"NO_MATCHING_KEY\"\n | \"ENCRYPTION_FAILED\"\n | \"NAVIGATION_UNAVAILABLE\";\n\n/**\n * The single error type thrown by every public API in this package. Always\n * carries a stable {@link ErrorCode} on `code` and, where relevant, the\n * underlying error on `cause`.\n */\nexport class AddToKlarnaError extends Error {\n public readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"AddToKlarnaError\";\n this.code = code;\n // Restore prototype chain for ES5-targeted bundles. No-op on modern targets.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport function isAddToKlarnaError(err: unknown): err is AddToKlarnaError {\n return err instanceof AddToKlarnaError;\n}\n","/**\n * Encode a UTF-8 string as base64url, without padding. Works in both Node\n * and browsers.\n *\n * Used to wrap the JWE compact serialization into a single URL path segment\n * (without the wrap, the dots inside the JWE break path matching on Klarna's\n * web router).\n */\nexport function toBase64Url(input: string): string {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(input, \"utf8\").toString(\"base64url\");\n }\n const bytes = new TextEncoder().encode(input);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/**\n * Decode a base64url string back to UTF-8. Inverse of {@link toBase64Url}.\n * Exported mostly so tests can assert on the round-trip.\n */\nexport function fromBase64Url(input: string): string {\n const padded =\n input.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"==\".slice(0, (4 - (input.length % 4)) % 4);\n\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(padded, \"base64\").toString(\"utf8\");\n }\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder().decode(bytes);\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport { toBase64Url } from \"./utils.js\";\n\n/**\n * Compose the deep-link *path* that goes inside the outer AppsFlyer OneLink's\n * `deep_link_value` parameter:\n *\n * <linkPath>/<brandNickname>/<encryptedSegment>\n *\n * `linkPath` is a rooted path like `/loyalty-cards-v2/add-to-klarna` (see\n * {@link resolveConfig}). `encryptedSegment` is the JWE compact serialization\n * wrapped in a single base64url — the wrap is required because the dots in the\n * JWE compact form would otherwise break Klarna's in-app router path matching.\n *\n * Returns a path (not a full URL): the composed value is meant to be passed\n * verbatim to {@link buildOneLinkUrl} as `deepLinkValue`, which handles the\n * outer URL encoding.\n */\nexport function buildLinkPath(linkPath: string, brandNickname: string, jweCompact: string): string {\n if (typeof brandNickname !== \"string\" || brandNickname.length === 0) {\n throw new AddToKlarnaError(\"INVALID_INPUT\", \"`brandNickname` must be a non-empty string.\");\n }\n if (/[\\s/?#]/.test(brandNickname)) {\n throw new AddToKlarnaError(\n \"INVALID_INPUT\",\n `\\`brandNickname\\` contains invalid characters (whitespace, \"/\", \"?\" or \"#\"): ${brandNickname}`,\n );\n }\n if (typeof jweCompact !== \"string\" || jweCompact.length === 0) {\n throw new AddToKlarnaError(\"ENCRYPTION_FAILED\", \"Empty JWE returned from encryptor.\");\n }\n const encryptedPayload = toBase64Url(jweCompact);\n return `${linkPath}/${encodeURIComponent(brandNickname)}/${encryptedPayload}`;\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport type { DecryptedPayload } from \"./types.js\";\n\n/**\n * Build the plaintext payload that goes inside the JWE.\n *\n * The schema is `{ inputId, linkId, iat }`. Klarna's backend validates this\n * shape on decryption, so the field names and types must remain stable.\n */\nexport function buildPayload(inputId: string): DecryptedPayload {\n if (typeof inputId !== \"string\" || inputId.length === 0) {\n throw new AddToKlarnaError(\"INVALID_INPUT\", \"`inputId` must be a non-empty string.\");\n }\n return {\n inputId,\n linkId: globalThis.crypto.randomUUID(),\n iat: Math.floor(Date.now() / 1000),\n };\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport type { ClientOptions, ClientTarget, Environment, Region } from \"./types.js\";\n\n/**\n * Resolved configuration used internally. Everything has been collapsed down\n * to concrete strings by the time we construct one.\n */\nexport interface ResolvedConfig {\n jwksUrl: string;\n /**\n * The in-app deep-link path prefix. Combined with the brand nickname and\n * encrypted payload by {@link buildLinkPath}, then passed as\n * `deep_link_value` on the outer AppsFlyer OneLink.\n */\n linkPath: string;\n /**\n * AppsFlyer OneLink prefix that wraps the encrypted deep link. See\n * {@link buildOneLinkUrl}.\n */\n oneLinkBaseUrl: string;\n /**\n * Custom URI scheme for the targeted Klarna app, including `://`.\n * {@link buildOneLinkUrl} appends the in-app path and sends the result as\n * `af_dp`. Selected by `clientTarget`, not by `environment`.\n */\n appUriScheme: string;\n /**\n * Desktop fallback URL (`af_web_dp`). Always the production add-to-klarna\n * page — not selected by `environment` or `clientTarget`.\n */\n desktopFallbackUrl: string;\n /** AppsFlyer media source (`pid`). */\n mediaSource: string;\n /** AppsFlyer campaign name (`c`). */\n campaignName: string;\n region: Region;\n}\n\n/**\n * JWKS endpoints per environment. **Independent of region.** Every published\n * JWKS file contains keys for every region, namespaced by a `kid-{region}-`\n * prefix; the picker selects the right one.\n */\nconst JWKS_URLS: Record<Environment, string> = {\n production: \"https://app.klarna.com/.well-known/jwks.json\",\n staging:\n \"https://eu-non-production-klarna-static-assets.s3.eu-west-1.amazonaws.com/klapp/.well-known/jwks.json\",\n};\n\n/**\n * In-app deep-link path prefixes. Combined with `<brandNickname>/<encrypted>`\n * to form the value of `deep_link_value` on the outer AppsFlyer OneLink. The\n * Klarna app parses this path and routes to the add-to-klarna screen.\n */\nconst LINK_PATHS: Record<Environment, string> = {\n production: \"/loyalty-cards-v2/add-to-klarna\",\n staging: \"/loyalty-cards-v2/add-to-klarna\",\n};\n\n/**\n * AppsFlyer OneLink prefixes. Production uses Klarna's custom `l.klarna.com`\n * short domain; staging uses the standard AppsFlyer `*.onelink.me` host that\n * targets the staging app build.\n */\nconst ONELINK_BASE_URLS: Record<ClientTarget, string> = {\n pink: \"https://l.klarna.com/22XC\",\n internalpink: \"https://klarnainternalpink.onelink.me/lXgD\",\n yellow: \"https://klarnayellow.onelink.me/JQ8X\",\n staging: \"https://klarnastaging.onelink.me/hV1K\",\n oneoff: \"https://klarnaoneoff.onelink.me/FaEr\",\n local: \"https://klarnalocal.onelink.me/dxUs\",\n};\n\n/**\n * Custom URI schemes, one per `clientTarget`. These match the schemes the\n * Klarna app registers (`klarna` plus the build suffix). {@link buildOneLinkUrl}\n * appends the in-app path before sending the result as `af_dp`.\n */\nconst APP_URI_SCHEMES: Record<ClientTarget, string> = {\n pink: \"klarna://\",\n internalpink: \"klarnainternalpink://\",\n yellow: \"klarnayellow://\",\n staging: \"klarnastaging://\",\n oneoff: \"klarnaoneoff://\",\n local: \"klarnadev://\",\n};\n\n/**\n * Desktop fallback (`af_web_dp`). One production page for every environment\n * and client target — there is no staging-specific fallback.\n */\nconst DESKTOP_FALLBACK_URL = \"https://klarna.com/add-to-klarna\";\n\n/**\n * AppsFlyer `pid` (media source).\n */\nconst MEDIA_SOURCE = \"WebApp\";\n\n/**\n * AppsFlyer `c` (campaign name).\n */\nconst CAMPAIGN_NAME = \"add-to-klarna\";\n\n/**\n * Resolve user-facing client options into a concrete config.\n *\n * Validates inputs up-front so any misconfiguration surfaces as an\n * `INVALID_CONFIG` `AddToKlarnaError` *before* the merchant page issues any\n * network calls.\n */\nexport function resolveConfig(options: ClientOptions): ResolvedConfig {\n const {\n environment = \"production\",\n clientTarget = \"pink\",\n region,\n } = options ?? ({} as ClientOptions);\n\n if (environment !== \"production\" && environment !== \"staging\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown environment: ${String(environment)}. Expected \"production\" or \"staging\".`,\n );\n }\n if (region !== \"eu\" && region !== \"us\" && region !== \"ap\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown region: ${String(region)}. Expected \"eu\", \"us\" or \"ap\".`,\n );\n }\n if (\n clientTarget !== \"pink\" &&\n clientTarget !== \"internalpink\" &&\n clientTarget !== \"yellow\" &&\n clientTarget !== \"staging\" &&\n clientTarget !== \"oneoff\" &&\n clientTarget !== \"local\"\n ) {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown clientTarget: ${String(clientTarget)}. Expected \"pink\", \"internalpink\", \"yellow\", \"staging\", \"oneoff\" or \"local\"`,\n );\n }\n\n return {\n jwksUrl: JWKS_URLS[environment],\n linkPath: LINK_PATHS[environment],\n oneLinkBaseUrl: ONELINK_BASE_URLS[clientTarget],\n appUriScheme: APP_URI_SCHEMES[clientTarget],\n desktopFallbackUrl: DESKTOP_FALLBACK_URL,\n mediaSource: MEDIA_SOURCE,\n campaignName: CAMPAIGN_NAME,\n region,\n };\n}\n","import { EncryptJWT, importJWK, type JWK } from \"jose\";\n\nimport { AddToKlarnaError } from \"./errors.js\";\nimport type { DecryptedPayload } from \"./types.js\";\n\n/**\n * The JWE profile this library produces. Pinned end-to-end:\n *\n * - `ALG` (key-encryption algorithm)\n * - `ENC` (content-encryption algorithm)\n *\n * `pickEncryptionKey` enforces the matching `kty`/`crv` on the JWKS side,\n * so a JWKS that drifts off this profile fails closed with `NO_MATCHING_KEY`\n * instead of silently producing a non-decryptable link. Changing either\n * constant is an SDK-coordinated event with Klarna's backend.\n */\nexport const ALG = \"ECDH-ES+A256KW\";\nexport const ENC = \"A256GCM\";\n\n/**\n * Encrypt the plaintext payload as a JWE using the given JWK. Produces the\n * compact serialization (five dot-separated base64url segments).\n */\nexport async function encryptJwe(\n payload: DecryptedPayload,\n key: JWK & { kid: string },\n): Promise<string> {\n let imported: Awaited<ReturnType<typeof importJWK>>;\n try {\n imported = await importJWK(key, ALG);\n } catch (cause) {\n throw new AddToKlarnaError(\n \"ENCRYPTION_FAILED\",\n `Could not import JWK with kid=\"${key.kid}\" for alg=\"${ALG}\".`,\n { cause },\n );\n }\n\n try {\n return await new EncryptJWT({\n inputId: payload.inputId,\n linkId: payload.linkId,\n })\n .setProtectedHeader({\n alg: ALG,\n enc: ENC,\n kid: key.kid,\n typ: \"JWT\",\n cty: \"application/json\",\n })\n .setIssuedAt(payload.iat)\n .encrypt(imported);\n } catch (cause) {\n throw new AddToKlarnaError(\"ENCRYPTION_FAILED\", `JWE encryption failed for kid=\"${key.kid}\".`, {\n cause,\n });\n }\n}\n","import type { JWK } from \"jose\";\n\nimport { ALG } from \"./encryptJwe.js\";\nimport { AddToKlarnaError } from \"./errors.js\";\nimport type { Region } from \"./types.js\";\n\n/**\n * The minimal JWKS shape we depend on. The actual file may contain extra\n * fields per RFC 7517; we ignore everything we do not need.\n */\nexport interface Jwks {\n keys: Array<JWK & { kid?: string; alg?: string; use?: string }>;\n}\n\n/**\n * The single key shape this library accepts off the JWKS. Pinned to the\n * tuple Klarna's backend publishes today:\n *\n * alg = ECDH-ES+A256KW\n * kty = EC\n * crv = P-256\n *\n * Anything else is rejected with `NO_MATCHING_KEY`, so a JWKS that ever\n * drifts off this profile — whether by mistake or because someone\n * managed to substitute it past TLS — fails closed instead of\n * silently using a weaker or symmetric algorithm. Any change to this\n * tuple is an SDK-coordinated event with the backend.\n */\nconst REQUIRED_KTY = \"EC\";\nconst REQUIRED_CRV = \"P-256\";\n\n/**\n * Fetch and parse the JWKS at `url`. There is no in-memory cache — caching is\n * delegated to the HTTP layer and Klarna's CDN. Wraps every failure mode in\n * a typed `AddToKlarnaError`.\n */\nexport async function fetchJwks(url: string): Promise<Jwks> {\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n });\n } catch (cause) {\n throw new AddToKlarnaError(\"JWKS_FETCH_FAILED\", `Failed to fetch JWKS from ${url}`, { cause });\n }\n\n if (!response.ok) {\n throw new AddToKlarnaError(\n \"JWKS_FETCH_FAILED\",\n `JWKS fetch returned HTTP ${response.status} ${response.statusText} for ${url}`,\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch (cause) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS response body was not valid JSON.\", { cause });\n }\n\n return assertJwks(body);\n}\n\n/**\n * Pick the encryption key for `region` from a JWKS document.\n *\n * A key is usable iff it declares all of:\n *\n * - `use === \"enc\"`\n * - `kid` starting with `kid-{region}-`\n * - `alg === ALG`, `kty === \"EC\"`, `crv === \"P-256\"` (see file header)\n *\n * If multiple keys match, the first one in document order wins — the JWKS\n * is published with the most recent key first. Throws `NO_MATCHING_KEY`\n * when nothing matches.\n */\nexport function pickEncryptionKey(jwks: Jwks, region: Region): JWK & { kid: string } {\n const prefix = `kid-${region}-`;\n const chosen = jwks.keys.find(\n (k) =>\n k.use === \"enc\" &&\n typeof k.kid === \"string\" &&\n k.kid.startsWith(prefix) &&\n k.alg === ALG &&\n k.kty === REQUIRED_KTY &&\n k.crv === REQUIRED_CRV,\n );\n\n if (!chosen || typeof chosen.kid !== \"string\") {\n throw new AddToKlarnaError(\n \"NO_MATCHING_KEY\",\n `JWKS contains no usable encryption key with kid prefix \"${prefix}\" ` +\n `(need use=\"enc\", alg=\"${ALG}\", kty=\"${REQUIRED_KTY}\", crv=\"${REQUIRED_CRV}\").`,\n );\n }\n return chosen as JWK & { kid: string };\n}\n\nfunction assertJwks(body: unknown): Jwks {\n if (!body || typeof body !== \"object\" || !(\"keys\" in body)) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS response is missing a `keys` array.\");\n }\n const keys = (body as { keys: unknown }).keys;\n if (!Array.isArray(keys)) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS `keys` is not an array.\");\n }\n return { keys: keys as Jwks[\"keys\"] };\n}\n","import { AddToKlarnaError } from \"./errors.js\";\n\n/**\n * Inputs for {@link buildOneLinkUrl}.\n *\n * All fields are required — this function is deliberately dumb: it takes\n * concrete URLs and composes them into an AppsFlyer OneLink. Environment\n * defaults live in {@link resolveConfig}.\n */\nexport interface BuildOneLinkInput {\n /** The AppsFlyer OneLink prefix (e.g. `https://l.klarna.com/22XC`). */\n oneLinkBaseUrl: string;\n\n /** AppsFlyer media source. Sent as `pid`. */\n pid: string;\n\n /** AppsFlyer campaign name. Sent as `c`. */\n c: string;\n\n /**\n * The in-app deep link path. Sent as `deep_link_value`. AppsFlyer hands\n * this string to the app for in-app routing once the app is open, and\n * replays it after install when the app was not installed (deferred deep\n * linking). It does not launch the app.\n */\n deepLinkValue: string;\n\n /**\n * Custom URI scheme for the targeted Klarna build, including `://`\n * (for example `klarna://` or `klarnaoneoff://`). Combined with\n * `deepLinkValue` and sent as `af_dp`, so the address that launches the\n * app is also the screen.\n */\n appUriScheme: string;\n\n /** Desktop fallback URL. Sent as `af_web_dp`. */\n webFallbackUrl: string;\n}\n\n/**\n * Compose an AppsFlyer OneLink URL that:\n *\n * - launches the installed Klarna app directly to `deepLinkValue` via `af_dp`,\n * - falls back to the App Store / Play Store (configured on the OneLink\n * template, not on this URL) when the app is not installed on mobile,\n * - falls back to `webFallbackUrl` on desktop.\n *\n * The returned URL has the shape:\n *\n * <oneLinkBaseUrl>?pid=<pid>&c=<c>&af_dp=<url-encoded scheme + deepLinkValue>\n * &deep_link_value=<url-encoded deepLinkValue>\n * &af_web_dp=<url-encoded webFallbackUrl>\n *\n * `af_dp` uses two slashes (`klarnaoneoff://loyalty-cards-v2/...`). The Klarna\n * app inserts the third slash before matching the path. A bare scheme opens\n * the app and leaves it on the home screen, because that launch address has\n * no route.\n *\n * Throws `AddToKlarnaError` with `INVALID_CONFIG` if any URL or the app URI\n * scheme is missing or malformed, or `INVALID_INPUT` if `deepLinkValue` is\n * empty.\n */\nexport function buildOneLinkUrl(input: BuildOneLinkInput): string {\n requireNonEmptyString(\"oneLinkBaseUrl\", input.oneLinkBaseUrl, \"INVALID_CONFIG\");\n requireNonEmptyString(\"pid\", input.pid, \"INVALID_CONFIG\");\n requireNonEmptyString(\"c\", input.c, \"INVALID_CONFIG\");\n requireNonEmptyString(\"appUriScheme\", input.appUriScheme, \"INVALID_CONFIG\");\n requireNonEmptyString(\"deepLinkValue\", input.deepLinkValue, \"INVALID_INPUT\");\n requireHttpUrl(\"webFallbackUrl\", input.webFallbackUrl);\n\n // Insertion order is preserved by URLSearchParams / Object.entries, and it\n // matters here only for output stability (easier to snapshot in tests).\n const params: Array<[string, string]> = [\n [\"pid\", input.pid],\n [\"c\", input.c],\n [\"af_dp\", buildAppDeepLink(input.appUriScheme, input.deepLinkValue)],\n [\"deep_link_value\", input.deepLinkValue],\n [\"af_web_dp\", input.webFallbackUrl],\n ];\n\n const query = params.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(\"&\");\n\n return `${input.oneLinkBaseUrl}?${query}`;\n}\n\n/**\n * `scheme://` + path, without a leading slash on the path, so the result is\n * `klarnaoneoff://loyalty-cards-v2/add-to-klarna/...` rather than three slashes.\n */\nfunction buildAppDeepLink(appUriScheme: string, deepLinkValue: string): string {\n const path = deepLinkValue.startsWith(\"/\") ? deepLinkValue.slice(1) : deepLinkValue;\n return `${appUriScheme}${path}`;\n}\n\nfunction requireNonEmptyString(\n name: string,\n value: unknown,\n code: \"INVALID_CONFIG\" | \"INVALID_INPUT\",\n): void {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new AddToKlarnaError(code, `\\`${name}\\` must be a non-empty string.`);\n }\n}\n\nfunction requireHttpUrl(name: string, value: unknown): void {\n requireNonEmptyString(name, value, \"INVALID_CONFIG\");\n let url: URL;\n try {\n url = new URL(value as string);\n } catch {\n throw new AddToKlarnaError(\"INVALID_CONFIG\", `\\`${name}\\` is not a valid URL: ${value}`);\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `\\`${name}\\` must use http or https. Got: ${value}`,\n );\n }\n}\n","import { buildLinkPath } from \"./buildLinkPath.js\";\nimport { buildPayload } from \"./buildPayload.js\";\nimport { resolveConfig } from \"./config.js\";\nimport { encryptJwe } from \"./encryptJwe.js\";\nimport { AddToKlarnaError } from \"./errors.js\";\nimport { fetchJwks, pickEncryptionKey } from \"./jwksClient.js\";\nimport { buildOneLinkUrl } from \"./oneLink.js\";\nimport type { AddToKlarnaClient, BuildLinkInput, ClientOptions } from \"./types.js\";\n\n/**\n * Create an Add-to-Klarna client.\n *\n * Construction is cheap and synchronous: it validates options and wires up\n * the pipeline. The first network call happens on the first\n * {@link AddToKlarnaClient.buildLink} / {@link AddToKlarnaClient.redirect}.\n *\n * Each `buildLink` returns an AppsFlyer OneLink that:\n *\n * - opens the Klarna app to the encrypted add-to-klarna deep link when the\n * app is installed,\n * - falls back to the App Store / Play Store on mobile when the app is not\n * installed (deferred deep link replays after install),\n * - falls back to `https://klarna.com/add-to-klarna` on desktop.\n *\n * Every `buildLink` re-fetches the JWKS — caching is handled by the HTTP\n * layer and Klarna's CDN, not in memory.\n */\nexport function createAddToKlarnaClient(options: ClientOptions): AddToKlarnaClient {\n const config = resolveConfig(options);\n\n async function buildLink(input: BuildLinkInput): Promise<string> {\n if (!input || typeof input !== \"object\") {\n throw new AddToKlarnaError(\n \"INVALID_INPUT\",\n \"buildLink() requires an object with `brandNickname` and `inputId`.\",\n );\n }\n const payload = buildPayload(input.inputId);\n const jwks = await fetchJwks(config.jwksUrl);\n const key = pickEncryptionKey(jwks, config.region);\n const jwe = await encryptJwe(payload, key);\n const deepLinkValue = buildLinkPath(config.linkPath, input.brandNickname, jwe);\n return buildOneLinkUrl({\n oneLinkBaseUrl: config.oneLinkBaseUrl,\n pid: config.mediaSource,\n c: config.campaignName,\n appUriScheme: config.appUriScheme,\n deepLinkValue,\n webFallbackUrl: config.desktopFallbackUrl,\n });\n }\n\n async function redirect(input: BuildLinkInput): Promise<void> {\n const url = await buildLink(input);\n const w = (globalThis as { window?: { location?: { assign?: (u: string) => void } } }).window;\n const assign = w?.location?.assign;\n if (typeof assign !== \"function\") {\n throw new AddToKlarnaError(\n \"NAVIGATION_UNAVAILABLE\",\n \"redirect() requires a browser context with `window.location.assign`. Use buildLink() and navigate yourself in non-browser environments.\",\n );\n }\n assign.call(w!.location, url);\n }\n\n return { buildLink, redirect };\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -74,6 +74,14 @@ var ONELINK_BASE_URLS = {
|
|
|
74
74
|
oneoff: "https://klarnaoneoff.onelink.me/FaEr",
|
|
75
75
|
local: "https://klarnalocal.onelink.me/dxUs"
|
|
76
76
|
};
|
|
77
|
+
var APP_URI_SCHEMES = {
|
|
78
|
+
pink: "klarna://",
|
|
79
|
+
internalpink: "klarnainternalpink://",
|
|
80
|
+
yellow: "klarnayellow://",
|
|
81
|
+
staging: "klarnastaging://",
|
|
82
|
+
oneoff: "klarnaoneoff://",
|
|
83
|
+
local: "klarnadev://"
|
|
84
|
+
};
|
|
77
85
|
var DESKTOP_FALLBACK_URL = "https://klarna.com/add-to-klarna";
|
|
78
86
|
var MEDIA_SOURCE = "WebApp";
|
|
79
87
|
var CAMPAIGN_NAME = "add-to-klarna";
|
|
@@ -105,6 +113,7 @@ function resolveConfig(options) {
|
|
|
105
113
|
jwksUrl: JWKS_URLS[environment],
|
|
106
114
|
linkPath: LINK_PATHS[environment],
|
|
107
115
|
oneLinkBaseUrl: ONELINK_BASE_URLS[clientTarget],
|
|
116
|
+
appUriScheme: APP_URI_SCHEMES[clientTarget],
|
|
108
117
|
desktopFallbackUrl: DESKTOP_FALLBACK_URL,
|
|
109
118
|
mediaSource: MEDIA_SOURCE,
|
|
110
119
|
campaignName: CAMPAIGN_NAME,
|
|
@@ -198,17 +207,23 @@ function buildOneLinkUrl(input) {
|
|
|
198
207
|
requireNonEmptyString("oneLinkBaseUrl", input.oneLinkBaseUrl, "INVALID_CONFIG");
|
|
199
208
|
requireNonEmptyString("pid", input.pid, "INVALID_CONFIG");
|
|
200
209
|
requireNonEmptyString("c", input.c, "INVALID_CONFIG");
|
|
210
|
+
requireNonEmptyString("appUriScheme", input.appUriScheme, "INVALID_CONFIG");
|
|
201
211
|
requireNonEmptyString("deepLinkValue", input.deepLinkValue, "INVALID_INPUT");
|
|
202
212
|
requireHttpUrl("webFallbackUrl", input.webFallbackUrl);
|
|
203
213
|
const params = [
|
|
204
214
|
["pid", input.pid],
|
|
205
215
|
["c", input.c],
|
|
216
|
+
["af_dp", buildAppDeepLink(input.appUriScheme, input.deepLinkValue)],
|
|
206
217
|
["deep_link_value", input.deepLinkValue],
|
|
207
218
|
["af_web_dp", input.webFallbackUrl]
|
|
208
219
|
];
|
|
209
220
|
const query = params.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
|
|
210
221
|
return `${input.oneLinkBaseUrl}?${query}`;
|
|
211
222
|
}
|
|
223
|
+
function buildAppDeepLink(appUriScheme, deepLinkValue) {
|
|
224
|
+
const path = deepLinkValue.startsWith("/") ? deepLinkValue.slice(1) : deepLinkValue;
|
|
225
|
+
return `${appUriScheme}${path}`;
|
|
226
|
+
}
|
|
212
227
|
function requireNonEmptyString(name, value, code) {
|
|
213
228
|
if (typeof value !== "string" || value.length === 0) {
|
|
214
229
|
throw new AddToKlarnaError(code, `\`${name}\` must be a non-empty string.`);
|
|
@@ -249,6 +264,7 @@ function createAddToKlarnaClient(options) {
|
|
|
249
264
|
oneLinkBaseUrl: config.oneLinkBaseUrl,
|
|
250
265
|
pid: config.mediaSource,
|
|
251
266
|
c: config.campaignName,
|
|
267
|
+
appUriScheme: config.appUriScheme,
|
|
252
268
|
deepLinkValue,
|
|
253
269
|
webFallbackUrl: config.desktopFallbackUrl
|
|
254
270
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/utils.ts","../src/buildLinkPath.ts","../src/buildPayload.ts","../src/config.ts","../src/encryptJwe.ts","../src/jwksClient.ts","../src/oneLink.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Stable error codes. Merchant integrations should branch on these, not on\n * the human-readable message.\n */\nexport type ErrorCode =\n | \"INVALID_INPUT\"\n | \"INVALID_CONFIG\"\n | \"JWKS_FETCH_FAILED\"\n | \"JWKS_INVALID\"\n | \"NO_MATCHING_KEY\"\n | \"ENCRYPTION_FAILED\"\n | \"NAVIGATION_UNAVAILABLE\";\n\n/**\n * The single error type thrown by every public API in this package. Always\n * carries a stable {@link ErrorCode} on `code` and, where relevant, the\n * underlying error on `cause`.\n */\nexport class AddToKlarnaError extends Error {\n public readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"AddToKlarnaError\";\n this.code = code;\n // Restore prototype chain for ES5-targeted bundles. No-op on modern targets.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport function isAddToKlarnaError(err: unknown): err is AddToKlarnaError {\n return err instanceof AddToKlarnaError;\n}\n","/**\n * Encode a UTF-8 string as base64url, without padding. Works in both Node\n * and browsers.\n *\n * Used to wrap the JWE compact serialization into a single URL path segment\n * (without the wrap, the dots inside the JWE break path matching on Klarna's\n * web router).\n */\nexport function toBase64Url(input: string): string {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(input, \"utf8\").toString(\"base64url\");\n }\n const bytes = new TextEncoder().encode(input);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/**\n * Decode a base64url string back to UTF-8. Inverse of {@link toBase64Url}.\n * Exported mostly so tests can assert on the round-trip.\n */\nexport function fromBase64Url(input: string): string {\n const padded =\n input.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"==\".slice(0, (4 - (input.length % 4)) % 4);\n\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(padded, \"base64\").toString(\"utf8\");\n }\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder().decode(bytes);\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport { toBase64Url } from \"./utils.js\";\n\n/**\n * Compose the deep-link *path* that goes inside the outer AppsFlyer OneLink's\n * `deep_link_value` parameter:\n *\n * <linkPath>/<brandNickname>/<encryptedSegment>\n *\n * `linkPath` is a rooted path like `/loyalty-cards-v2/add-to-klarna` (see\n * {@link resolveConfig}). `encryptedSegment` is the JWE compact serialization\n * wrapped in a single base64url — the wrap is required because the dots in the\n * JWE compact form would otherwise break Klarna's in-app router path matching.\n *\n * Returns a path (not a full URL): the composed value is meant to be passed\n * verbatim to {@link buildOneLinkUrl} as `deepLinkValue`, which handles the\n * outer URL encoding.\n */\nexport function buildLinkPath(linkPath: string, brandNickname: string, jweCompact: string): string {\n if (typeof brandNickname !== \"string\" || brandNickname.length === 0) {\n throw new AddToKlarnaError(\"INVALID_INPUT\", \"`brandNickname` must be a non-empty string.\");\n }\n if (/[\\s/?#]/.test(brandNickname)) {\n throw new AddToKlarnaError(\n \"INVALID_INPUT\",\n `\\`brandNickname\\` contains invalid characters (whitespace, \"/\", \"?\" or \"#\"): ${brandNickname}`,\n );\n }\n if (typeof jweCompact !== \"string\" || jweCompact.length === 0) {\n throw new AddToKlarnaError(\"ENCRYPTION_FAILED\", \"Empty JWE returned from encryptor.\");\n }\n const encryptedPayload = toBase64Url(jweCompact);\n return `${linkPath}/${encodeURIComponent(brandNickname)}/${encryptedPayload}`;\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport type { DecryptedPayload } from \"./types.js\";\n\n/**\n * Build the plaintext payload that goes inside the JWE.\n *\n * The schema is `{ inputId, linkId, iat }`. Klarna's backend validates this\n * shape on decryption, so the field names and types must remain stable.\n */\nexport function buildPayload(inputId: string): DecryptedPayload {\n if (typeof inputId !== \"string\" || inputId.length === 0) {\n throw new AddToKlarnaError(\"INVALID_INPUT\", \"`inputId` must be a non-empty string.\");\n }\n return {\n inputId,\n linkId: globalThis.crypto.randomUUID(),\n iat: Math.floor(Date.now() / 1000),\n };\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport type { ClientOptions, ClientTarget, Environment, Region } from \"./types.js\";\n\n/**\n * Resolved configuration used internally. Everything has been collapsed down\n * to concrete strings by the time we construct one.\n */\nexport interface ResolvedConfig {\n jwksUrl: string;\n /**\n * The in-app deep-link path prefix. Combined with the brand nickname and\n * encrypted payload by {@link buildLinkPath}, then passed as\n * `deep_link_value` on the outer AppsFlyer OneLink.\n */\n linkPath: string;\n /**\n * AppsFlyer OneLink prefix that wraps the encrypted deep link. See\n * {@link buildOneLinkUrl}.\n */\n oneLinkBaseUrl: string;\n /**\n * Desktop fallback URL (`af_web_dp`). Always the production add-to-klarna\n * page — not selected by `environment` or `clientTarget`.\n */\n desktopFallbackUrl: string;\n /** AppsFlyer media source (`pid`). */\n mediaSource: string;\n /** AppsFlyer campaign name (`c`). */\n campaignName: string;\n region: Region;\n}\n\n/**\n * JWKS endpoints per environment. **Independent of region.** Every published\n * JWKS file contains keys for every region, namespaced by a `kid-{region}-`\n * prefix; the picker selects the right one.\n */\nconst JWKS_URLS: Record<Environment, string> = {\n production: \"https://app.klarna.com/.well-known/jwks.json\",\n staging:\n \"https://eu-non-production-klarna-static-assets.s3.eu-west-1.amazonaws.com/klapp/.well-known/jwks.json\",\n};\n\n/**\n * In-app deep-link path prefixes. Combined with `<brandNickname>/<encrypted>`\n * to form the value of `deep_link_value` on the outer AppsFlyer OneLink. The\n * Klarna app parses this path and routes to the add-to-klarna screen.\n */\nconst LINK_PATHS: Record<Environment, string> = {\n production: \"/loyalty-cards-v2/add-to-klarna\",\n staging: \"/loyalty-cards-v2/add-to-klarna\",\n};\n\n/**\n * AppsFlyer OneLink prefixes. Production uses Klarna's custom `l.klarna.com`\n * short domain; staging uses the standard AppsFlyer `*.onelink.me` host that\n * targets the staging app build.\n */\nconst ONELINK_BASE_URLS: Record<ClientTarget, string> = {\n pink: \"https://l.klarna.com/22XC\",\n internalpink: \"https://klarnainternalpink.onelink.me/lXgD\",\n yellow: \"https://klarnayellow.onelink.me/JQ8X\",\n staging: \"https://klarnastaging.onelink.me/hV1K\",\n oneoff: \"https://klarnaoneoff.onelink.me/FaEr\",\n local: \"https://klarnalocal.onelink.me/dxUs\",\n};\n\n/**\n * Desktop fallback (`af_web_dp`). One production page for every environment\n * and client target — there is no staging-specific fallback.\n */\nconst DESKTOP_FALLBACK_URL = \"https://klarna.com/add-to-klarna\";\n\n/**\n * AppsFlyer `pid` (media source).\n */\nconst MEDIA_SOURCE = \"WebApp\";\n\n/**\n * AppsFlyer `c` (campaign name).\n */\nconst CAMPAIGN_NAME = \"add-to-klarna\";\n\n/**\n * Resolve user-facing client options into a concrete config.\n *\n * Validates inputs up-front so any misconfiguration surfaces as an\n * `INVALID_CONFIG` `AddToKlarnaError` *before* the merchant page issues any\n * network calls.\n */\nexport function resolveConfig(options: ClientOptions): ResolvedConfig {\n const {\n environment = \"production\",\n clientTarget = \"pink\",\n region,\n } = options ?? ({} as ClientOptions);\n\n if (environment !== \"production\" && environment !== \"staging\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown environment: ${String(environment)}. Expected \"production\" or \"staging\".`,\n );\n }\n if (region !== \"eu\" && region !== \"us\" && region !== \"ap\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown region: ${String(region)}. Expected \"eu\", \"us\" or \"ap\".`,\n );\n }\n if (\n clientTarget !== \"pink\" &&\n clientTarget !== \"internalpink\" &&\n clientTarget !== \"yellow\" &&\n clientTarget !== \"staging\" &&\n clientTarget !== \"oneoff\" &&\n clientTarget !== \"local\"\n ) {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown clientTarget: ${String(clientTarget)}. Expected \"pink\", \"internalpink\", \"yellow\", \"staging\", \"oneoff\" or \"local\"`,\n );\n }\n\n return {\n jwksUrl: JWKS_URLS[environment],\n linkPath: LINK_PATHS[environment],\n oneLinkBaseUrl: ONELINK_BASE_URLS[clientTarget],\n desktopFallbackUrl: DESKTOP_FALLBACK_URL,\n mediaSource: MEDIA_SOURCE,\n campaignName: CAMPAIGN_NAME,\n region,\n };\n}\n","import { EncryptJWT, importJWK, type JWK } from \"jose\";\n\nimport { AddToKlarnaError } from \"./errors.js\";\nimport type { DecryptedPayload } from \"./types.js\";\n\n/**\n * The JWE profile this library produces. Pinned end-to-end:\n *\n * - `ALG` (key-encryption algorithm)\n * - `ENC` (content-encryption algorithm)\n *\n * `pickEncryptionKey` enforces the matching `kty`/`crv` on the JWKS side,\n * so a JWKS that drifts off this profile fails closed with `NO_MATCHING_KEY`\n * instead of silently producing a non-decryptable link. Changing either\n * constant is an SDK-coordinated event with Klarna's backend.\n */\nexport const ALG = \"ECDH-ES+A256KW\";\nexport const ENC = \"A256GCM\";\n\n/**\n * Encrypt the plaintext payload as a JWE using the given JWK. Produces the\n * compact serialization (five dot-separated base64url segments).\n */\nexport async function encryptJwe(\n payload: DecryptedPayload,\n key: JWK & { kid: string },\n): Promise<string> {\n let imported: Awaited<ReturnType<typeof importJWK>>;\n try {\n imported = await importJWK(key, ALG);\n } catch (cause) {\n throw new AddToKlarnaError(\n \"ENCRYPTION_FAILED\",\n `Could not import JWK with kid=\"${key.kid}\" for alg=\"${ALG}\".`,\n { cause },\n );\n }\n\n try {\n return await new EncryptJWT({\n inputId: payload.inputId,\n linkId: payload.linkId,\n })\n .setProtectedHeader({\n alg: ALG,\n enc: ENC,\n kid: key.kid,\n typ: \"JWT\",\n cty: \"application/json\",\n })\n .setIssuedAt(payload.iat)\n .encrypt(imported);\n } catch (cause) {\n throw new AddToKlarnaError(\"ENCRYPTION_FAILED\", `JWE encryption failed for kid=\"${key.kid}\".`, {\n cause,\n });\n }\n}\n","import type { JWK } from \"jose\";\n\nimport { ALG } from \"./encryptJwe.js\";\nimport { AddToKlarnaError } from \"./errors.js\";\nimport type { Region } from \"./types.js\";\n\n/**\n * The minimal JWKS shape we depend on. The actual file may contain extra\n * fields per RFC 7517; we ignore everything we do not need.\n */\nexport interface Jwks {\n keys: Array<JWK & { kid?: string; alg?: string; use?: string }>;\n}\n\n/**\n * The single key shape this library accepts off the JWKS. Pinned to the\n * tuple Klarna's backend publishes today:\n *\n * alg = ECDH-ES+A256KW\n * kty = EC\n * crv = P-256\n *\n * Anything else is rejected with `NO_MATCHING_KEY`, so a JWKS that ever\n * drifts off this profile — whether by mistake or because someone\n * managed to substitute it past TLS — fails closed instead of\n * silently using a weaker or symmetric algorithm. Any change to this\n * tuple is an SDK-coordinated event with the backend.\n */\nconst REQUIRED_KTY = \"EC\";\nconst REQUIRED_CRV = \"P-256\";\n\n/**\n * Fetch and parse the JWKS at `url`. There is no in-memory cache — caching is\n * delegated to the HTTP layer and Klarna's CDN. Wraps every failure mode in\n * a typed `AddToKlarnaError`.\n */\nexport async function fetchJwks(url: string): Promise<Jwks> {\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n });\n } catch (cause) {\n throw new AddToKlarnaError(\"JWKS_FETCH_FAILED\", `Failed to fetch JWKS from ${url}`, { cause });\n }\n\n if (!response.ok) {\n throw new AddToKlarnaError(\n \"JWKS_FETCH_FAILED\",\n `JWKS fetch returned HTTP ${response.status} ${response.statusText} for ${url}`,\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch (cause) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS response body was not valid JSON.\", { cause });\n }\n\n return assertJwks(body);\n}\n\n/**\n * Pick the encryption key for `region` from a JWKS document.\n *\n * A key is usable iff it declares all of:\n *\n * - `use === \"enc\"`\n * - `kid` starting with `kid-{region}-`\n * - `alg === ALG`, `kty === \"EC\"`, `crv === \"P-256\"` (see file header)\n *\n * If multiple keys match, the first one in document order wins — the JWKS\n * is published with the most recent key first. Throws `NO_MATCHING_KEY`\n * when nothing matches.\n */\nexport function pickEncryptionKey(jwks: Jwks, region: Region): JWK & { kid: string } {\n const prefix = `kid-${region}-`;\n const chosen = jwks.keys.find(\n (k) =>\n k.use === \"enc\" &&\n typeof k.kid === \"string\" &&\n k.kid.startsWith(prefix) &&\n k.alg === ALG &&\n k.kty === REQUIRED_KTY &&\n k.crv === REQUIRED_CRV,\n );\n\n if (!chosen || typeof chosen.kid !== \"string\") {\n throw new AddToKlarnaError(\n \"NO_MATCHING_KEY\",\n `JWKS contains no usable encryption key with kid prefix \"${prefix}\" ` +\n `(need use=\"enc\", alg=\"${ALG}\", kty=\"${REQUIRED_KTY}\", crv=\"${REQUIRED_CRV}\").`,\n );\n }\n return chosen as JWK & { kid: string };\n}\n\nfunction assertJwks(body: unknown): Jwks {\n if (!body || typeof body !== \"object\" || !(\"keys\" in body)) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS response is missing a `keys` array.\");\n }\n const keys = (body as { keys: unknown }).keys;\n if (!Array.isArray(keys)) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS `keys` is not an array.\");\n }\n return { keys: keys as Jwks[\"keys\"] };\n}\n","import { AddToKlarnaError } from \"./errors.js\";\n\n/**\n * Inputs for {@link buildOneLinkUrl}.\n *\n * All fields are required — this function is deliberately dumb: it takes\n * concrete URLs and composes them into an AppsFlyer OneLink. Environment\n * defaults live in {@link resolveConfig}.\n */\nexport interface BuildOneLinkInput {\n /** The AppsFlyer OneLink prefix (e.g. `https://l.klarna.com/22XC`). */\n oneLinkBaseUrl: string;\n\n /** AppsFlyer media source. Sent as `pid`. */\n pid: string;\n\n /** AppsFlyer campaign name. Sent as `c`. */\n c: string;\n\n /**\n * The in-app deep link path. Sent as `deep_link_value`. When the Klarna app\n * is installed on the device, AppsFlyer hands this string to the app for\n * in-app routing; when the app is not installed, AppsFlyer stores it and\n * replays it after install (deferred deep linking).\n */\n deepLinkValue: string;\n\n /** Desktop fallback URL. Sent as `af_web_dp`. */\n webFallbackUrl: string;\n}\n\n/**\n * Compose an AppsFlyer OneLink URL that:\n *\n * - opens the Klarna app to the given `deepLinkValue` when the app is\n * installed on iOS or Android,\n * - falls back to the App Store / Play Store (configured on the OneLink\n * template, not on this URL) when the app is not installed on mobile,\n * - falls back to `webFallbackUrl` on desktop.\n *\n * The returned URL has the shape:\n *\n * <oneLinkBaseUrl>?pid=<pid>&c=<c>&deep_link_value=<url-encoded deepLinkValue>\n * &af_web_dp=<url-encoded webFallbackUrl>\n *\n * Throws `AddToKlarnaError` with `INVALID_CONFIG` if any URL is missing or\n * malformed, or `INVALID_INPUT` if `deepLinkValue` is empty.\n */\nexport function buildOneLinkUrl(input: BuildOneLinkInput): string {\n requireNonEmptyString(\"oneLinkBaseUrl\", input.oneLinkBaseUrl, \"INVALID_CONFIG\");\n requireNonEmptyString(\"pid\", input.pid, \"INVALID_CONFIG\");\n requireNonEmptyString(\"c\", input.c, \"INVALID_CONFIG\");\n requireNonEmptyString(\"deepLinkValue\", input.deepLinkValue, \"INVALID_INPUT\");\n requireHttpUrl(\"webFallbackUrl\", input.webFallbackUrl);\n\n // Insertion order is preserved by URLSearchParams / Object.entries, and it\n // matters here only for output stability (easier to snapshot in tests).\n const params: Array<[string, string]> = [\n [\"pid\", input.pid],\n [\"c\", input.c],\n [\"deep_link_value\", input.deepLinkValue],\n [\"af_web_dp\", input.webFallbackUrl],\n ];\n\n const query = params.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(\"&\");\n\n return `${input.oneLinkBaseUrl}?${query}`;\n}\n\nfunction requireNonEmptyString(\n name: string,\n value: unknown,\n code: \"INVALID_CONFIG\" | \"INVALID_INPUT\",\n): void {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new AddToKlarnaError(code, `\\`${name}\\` must be a non-empty string.`);\n }\n}\n\nfunction requireHttpUrl(name: string, value: unknown): void {\n requireNonEmptyString(name, value, \"INVALID_CONFIG\");\n let url: URL;\n try {\n url = new URL(value as string);\n } catch {\n throw new AddToKlarnaError(\"INVALID_CONFIG\", `\\`${name}\\` is not a valid URL: ${value}`);\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `\\`${name}\\` must use http or https. Got: ${value}`,\n );\n }\n}\n","import { buildLinkPath } from \"./buildLinkPath.js\";\nimport { buildPayload } from \"./buildPayload.js\";\nimport { resolveConfig } from \"./config.js\";\nimport { encryptJwe } from \"./encryptJwe.js\";\nimport { AddToKlarnaError } from \"./errors.js\";\nimport { fetchJwks, pickEncryptionKey } from \"./jwksClient.js\";\nimport { buildOneLinkUrl } from \"./oneLink.js\";\nimport type { AddToKlarnaClient, BuildLinkInput, ClientOptions } from \"./types.js\";\n\n/**\n * Create an Add-to-Klarna client.\n *\n * Construction is cheap and synchronous: it validates options and wires up\n * the pipeline. The first network call happens on the first\n * {@link AddToKlarnaClient.buildLink} / {@link AddToKlarnaClient.redirect}.\n *\n * Each `buildLink` returns an AppsFlyer OneLink that:\n *\n * - opens the Klarna app to the encrypted add-to-klarna deep link when the\n * app is installed,\n * - falls back to the App Store / Play Store on mobile when the app is not\n * installed (deferred deep link replays after install),\n * - falls back to `https://klarna.com/add-to-klarna` on desktop.\n *\n * Every `buildLink` re-fetches the JWKS — caching is handled by the HTTP\n * layer and Klarna's CDN, not in memory.\n */\nexport function createAddToKlarnaClient(options: ClientOptions): AddToKlarnaClient {\n const config = resolveConfig(options);\n\n async function buildLink(input: BuildLinkInput): Promise<string> {\n if (!input || typeof input !== \"object\") {\n throw new AddToKlarnaError(\n \"INVALID_INPUT\",\n \"buildLink() requires an object with `brandNickname` and `inputId`.\",\n );\n }\n const payload = buildPayload(input.inputId);\n const jwks = await fetchJwks(config.jwksUrl);\n const key = pickEncryptionKey(jwks, config.region);\n const jwe = await encryptJwe(payload, key);\n const deepLinkValue = buildLinkPath(config.linkPath, input.brandNickname, jwe);\n return buildOneLinkUrl({\n oneLinkBaseUrl: config.oneLinkBaseUrl,\n pid: config.mediaSource,\n c: config.campaignName,\n deepLinkValue,\n webFallbackUrl: config.desktopFallbackUrl,\n });\n }\n\n async function redirect(input: BuildLinkInput): Promise<void> {\n const url = await buildLink(input);\n const w = (globalThis as { window?: { location?: { assign?: (u: string) => void } } }).window;\n const assign = w?.location?.assign;\n if (typeof assign !== \"function\") {\n throw new AddToKlarnaError(\n \"NAVIGATION_UNAVAILABLE\",\n \"redirect() requires a browser context with `window.location.assign`. Use buildLink() and navigate yourself in non-browser environments.\",\n );\n }\n assign.call(w!.location, url);\n }\n\n return { buildLink, redirect };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/utils.ts","../src/buildLinkPath.ts","../src/buildPayload.ts","../src/config.ts","../src/encryptJwe.ts","../src/jwksClient.ts","../src/oneLink.ts","../src/client.ts"],"names":[],"mappings":";;;AAkBO,IAAM,gBAAA,GAAN,cAA+B,KAAA,CAAM;AAAA,EAC1B,IAAA;AAAA,EAEhB,WAAA,CAAY,IAAA,EAAiB,OAAA,EAAiB,OAAA,EAA+B;AAC3E,IAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,IAAA,GAAO,kBAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAEZ,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,GAAA,CAAA,MAAA,CAAW,SAAS,CAAA;AAAA,EAClD;AACF;AAEO,SAAS,mBAAmB,GAAA,EAAuC;AACxE,EAAA,OAAO,GAAA,YAAe,gBAAA;AACxB;;;ACxBO,SAAS,YAAY,KAAA,EAAuB;AACjD,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,IAAA,OAAO,OAAO,IAAA,CAAK,KAAA,EAAO,MAAM,CAAA,CAAE,SAAS,WAAW,CAAA;AAAA,EACxD;AACA,EAAA,MAAM,KAAA,GAAQ,IAAI,WAAA,EAAY,CAAE,OAAO,KAAK,CAAA;AAC5C,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAA,IAAU,MAAA,CAAO,YAAA,CAAa,KAAA,CAAM,CAAC,CAAE,CAAA;AAAA,EACzC;AACA,EAAA,OAAO,IAAA,CAAK,MAAM,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC/E;;;ACAO,SAAS,aAAA,CAAc,QAAA,EAAkB,aAAA,EAAuB,UAAA,EAA4B;AACjG,EAAA,IAAI,OAAO,aAAA,KAAkB,QAAA,IAAY,aAAA,CAAc,WAAW,CAAA,EAAG;AACnE,IAAA,MAAM,IAAI,gBAAA,CAAiB,eAAA,EAAiB,6CAA6C,CAAA;AAAA,EAC3F;AACA,EAAA,IAAI,SAAA,CAAU,IAAA,CAAK,aAAa,CAAA,EAAG;AACjC,IAAA,MAAM,IAAI,gBAAA;AAAA,MACR,eAAA;AAAA,MACA,gFAAgF,aAAa,CAAA;AAAA,KAC/F;AAAA,EACF;AACA,EAAA,IAAI,OAAO,UAAA,KAAe,QAAA,IAAY,UAAA,CAAW,WAAW,CAAA,EAAG;AAC7D,IAAA,MAAM,IAAI,gBAAA,CAAiB,mBAAA,EAAqB,oCAAoC,CAAA;AAAA,EACtF;AACA,EAAA,MAAM,gBAAA,GAAmB,YAAY,UAAU,CAAA;AAC/C,EAAA,OAAO,GAAG,QAAQ,CAAA,CAAA,EAAI,mBAAmB,aAAa,CAAC,IAAI,gBAAgB,CAAA,CAAA;AAC7E;;;ACxBO,SAAS,aAAa,OAAA,EAAmC;AAC9D,EAAA,IAAI,OAAO,OAAA,KAAY,QAAA,IAAY,OAAA,CAAQ,WAAW,CAAA,EAAG;AACvD,IAAA,MAAM,IAAI,gBAAA,CAAiB,eAAA,EAAiB,uCAAuC,CAAA;AAAA,EACrF;AACA,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,MAAA,EAAQ,UAAA,CAAW,MAAA,CAAO,UAAA,EAAW;AAAA,IACrC,KAAK,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,KAAQ,GAAI;AAAA,GACnC;AACF;;;ACyBA,IAAM,SAAA,GAAyC;AAAA,EAC7C,UAAA,EAAY,8CAAA;AAAA,EACZ,OAAA,EACE;AACJ,CAAA;AAOA,IAAM,UAAA,GAA0C;AAAA,EAC9C,UAAA,EAAY,iCAAA;AAAA,EACZ,OAAA,EAAS;AACX,CAAA;AAOA,IAAM,iBAAA,GAAkD;AAAA,EACtD,IAAA,EAAM,2BAAA;AAAA,EACN,YAAA,EAAc,4CAAA;AAAA,EACd,MAAA,EAAQ,sCAAA;AAAA,EACR,OAAA,EAAS,uCAAA;AAAA,EACT,MAAA,EAAQ,sCAAA;AAAA,EACR,KAAA,EAAO;AACT,CAAA;AAOA,IAAM,eAAA,GAAgD;AAAA,EACpD,IAAA,EAAM,WAAA;AAAA,EACN,YAAA,EAAc,uBAAA;AAAA,EACd,MAAA,EAAQ,iBAAA;AAAA,EACR,OAAA,EAAS,kBAAA;AAAA,EACT,MAAA,EAAQ,iBAAA;AAAA,EACR,KAAA,EAAO;AACT,CAAA;AAMA,IAAM,oBAAA,GAAuB,kCAAA;AAK7B,IAAM,YAAA,GAAe,QAAA;AAKrB,IAAM,aAAA,GAAgB,eAAA;AASf,SAAS,cAAc,OAAA,EAAwC;AACpE,EAAA,MAAM;AAAA,IACJ,WAAA,GAAc,YAAA;AAAA,IACd,YAAA,GAAe,MAAA;AAAA,IACf;AAAA,GACF,GAAI,WAAY,EAAC;AAEjB,EAAA,IAAI,WAAA,KAAgB,YAAA,IAAgB,WAAA,KAAgB,SAAA,EAAW;AAC7D,IAAA,MAAM,IAAI,gBAAA;AAAA,MACR,gBAAA;AAAA,MACA,CAAA,qBAAA,EAAwB,MAAA,CAAO,WAAW,CAAC,CAAA,qCAAA;AAAA,KAC7C;AAAA,EACF;AACA,EAAA,IAAI,MAAA,KAAW,IAAA,IAAQ,MAAA,KAAW,IAAA,IAAQ,WAAW,IAAA,EAAM;AACzD,IAAA,MAAM,IAAI,gBAAA;AAAA,MACR,gBAAA;AAAA,MACA,CAAA,gBAAA,EAAmB,MAAA,CAAO,MAAM,CAAC,CAAA,8BAAA;AAAA,KACnC;AAAA,EACF;AACA,EAAA,IACE,YAAA,KAAiB,MAAA,IACjB,YAAA,KAAiB,cAAA,IACjB,YAAA,KAAiB,QAAA,IACjB,YAAA,KAAiB,SAAA,IACjB,YAAA,KAAiB,QAAA,IACjB,YAAA,KAAiB,OAAA,EACjB;AACA,IAAA,MAAM,IAAI,gBAAA;AAAA,MACR,gBAAA;AAAA,MACA,CAAA,sBAAA,EAAyB,MAAA,CAAO,YAAY,CAAC,CAAA,2EAAA;AAAA,KAC/C;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,UAAU,WAAW,CAAA;AAAA,IAC9B,QAAA,EAAU,WAAW,WAAW,CAAA;AAAA,IAChC,cAAA,EAAgB,kBAAkB,YAAY,CAAA;AAAA,IAC9C,YAAA,EAAc,gBAAgB,YAAY,CAAA;AAAA,IAC1C,kBAAA,EAAoB,oBAAA;AAAA,IACpB,WAAA,EAAa,YAAA;AAAA,IACb,YAAA,EAAc,aAAA;AAAA,IACd;AAAA,GACF;AACF;ACzIO,IAAM,GAAA,GAAM,gBAAA;AACZ,IAAM,GAAA,GAAM,SAAA;AAMnB,eAAsB,UAAA,CACpB,SACA,GAAA,EACiB;AACjB,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,MAAM,SAAA,CAAU,GAAA,EAAK,GAAG,CAAA;AAAA,EACrC,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,IAAI,gBAAA;AAAA,MACR,mBAAA;AAAA,MACA,CAAA,+BAAA,EAAkC,GAAA,CAAI,GAAG,CAAA,WAAA,EAAc,GAAG,CAAA,EAAA,CAAA;AAAA,MAC1D,EAAE,KAAA;AAAM,KACV;AAAA,EACF;AAEA,EAAA,IAAI;AACF,IAAA,OAAO,MAAM,IAAI,UAAA,CAAW;AAAA,MAC1B,SAAS,OAAA,CAAQ,OAAA;AAAA,MACjB,QAAQ,OAAA,CAAQ;AAAA,KACjB,EACE,kBAAA,CAAmB;AAAA,MAClB,GAAA,EAAK,GAAA;AAAA,MACL,GAAA,EAAK,GAAA;AAAA,MACL,KAAK,GAAA,CAAI,GAAA;AAAA,MACT,GAAA,EAAK,KAAA;AAAA,MACL,GAAA,EAAK;AAAA,KACN,CAAA,CACA,WAAA,CAAY,QAAQ,GAAG,CAAA,CACvB,QAAQ,QAAQ,CAAA;AAAA,EACrB,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,IAAI,gBAAA,CAAiB,mBAAA,EAAqB,CAAA,+BAAA,EAAkC,GAAA,CAAI,GAAG,CAAA,EAAA,CAAA,EAAM;AAAA,MAC7F;AAAA,KACD,CAAA;AAAA,EACH;AACF;;;AC7BA,IAAM,YAAA,GAAe,IAAA;AACrB,IAAM,YAAA,GAAe,OAAA;AAOrB,eAAsB,UAAU,GAAA,EAA4B;AAC1D,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,MAAM,MAAM,GAAA,EAAK;AAAA,MAC1B,MAAA,EAAQ,KAAA;AAAA,MACR,OAAA,EAAS,EAAE,MAAA,EAAQ,kBAAA;AAAmB,KACvC,CAAA;AAAA,EACH,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,IAAI,iBAAiB,mBAAA,EAAqB,CAAA,0BAAA,EAA6B,GAAG,CAAA,CAAA,EAAI,EAAE,OAAO,CAAA;AAAA,EAC/F;AAEA,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,IAAA,MAAM,IAAI,gBAAA;AAAA,MACR,mBAAA;AAAA,MACA,4BAA4B,QAAA,CAAS,MAAM,IAAI,QAAA,CAAS,UAAU,QAAQ,GAAG,CAAA;AAAA,KAC/E;AAAA,EACF;AAEA,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI;AACF,IAAA,IAAA,GAAO,MAAM,SAAS,IAAA,EAAK;AAAA,EAC7B,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,IAAI,gBAAA,CAAiB,cAAA,EAAgB,wCAAA,EAA0C,EAAE,OAAO,CAAA;AAAA,EAChG;AAEA,EAAA,OAAO,WAAW,IAAI,CAAA;AACxB;AAeO,SAAS,iBAAA,CAAkB,MAAY,MAAA,EAAuC;AACnF,EAAA,MAAM,MAAA,GAAS,OAAO,MAAM,CAAA,CAAA,CAAA;AAC5B,EAAA,MAAM,MAAA,GAAS,KAAK,IAAA,CAAK,IAAA;AAAA,IACvB,CAAC,MACC,CAAA,CAAE,GAAA,KAAQ,SACV,OAAO,CAAA,CAAE,QAAQ,QAAA,IACjB,CAAA,CAAE,IAAI,UAAA,CAAW,MAAM,KACvB,CAAA,CAAE,GAAA,KAAQ,OACV,CAAA,CAAE,GAAA,KAAQ,YAAA,IACV,CAAA,CAAE,GAAA,KAAQ;AAAA,GACd;AAEA,EAAA,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,CAAO,QAAQ,QAAA,EAAU;AAC7C,IAAA,MAAM,IAAI,gBAAA;AAAA,MACR,iBAAA;AAAA,MACA,2DAA2D,MAAM,CAAA,wBAAA,EACtC,GAAG,CAAA,QAAA,EAAW,YAAY,WAAW,YAAY,CAAA,GAAA;AAAA,KAC9E;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,WAAW,IAAA,EAAqB;AACvC,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,SAAS,QAAA,IAAY,EAAE,UAAU,IAAA,CAAA,EAAO;AAC1D,IAAA,MAAM,IAAI,gBAAA,CAAiB,cAAA,EAAgB,0CAA0C,CAAA;AAAA,EACvF;AACA,EAAA,MAAM,OAAQ,IAAA,CAA2B,IAAA;AACzC,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG;AACxB,IAAA,MAAM,IAAI,gBAAA,CAAiB,cAAA,EAAgB,8BAA8B,CAAA;AAAA,EAC3E;AACA,EAAA,OAAO,EAAE,IAAA,EAA2B;AACtC;;;AC9CO,SAAS,gBAAgB,KAAA,EAAkC;AAChE,EAAA,qBAAA,CAAsB,gBAAA,EAAkB,KAAA,CAAM,cAAA,EAAgB,gBAAgB,CAAA;AAC9E,EAAA,qBAAA,CAAsB,KAAA,EAAO,KAAA,CAAM,GAAA,EAAK,gBAAgB,CAAA;AACxD,EAAA,qBAAA,CAAsB,GAAA,EAAK,KAAA,CAAM,CAAA,EAAG,gBAAgB,CAAA;AACpD,EAAA,qBAAA,CAAsB,cAAA,EAAgB,KAAA,CAAM,YAAA,EAAc,gBAAgB,CAAA;AAC1E,EAAA,qBAAA,CAAsB,eAAA,EAAiB,KAAA,CAAM,aAAA,EAAe,eAAe,CAAA;AAC3E,EAAA,cAAA,CAAe,gBAAA,EAAkB,MAAM,cAAc,CAAA;AAIrD,EAAA,MAAM,MAAA,GAAkC;AAAA,IACtC,CAAC,KAAA,EAAO,KAAA,CAAM,GAAG,CAAA;AAAA,IACjB,CAAC,GAAA,EAAK,KAAA,CAAM,CAAC,CAAA;AAAA,IACb,CAAC,OAAA,EAAS,gBAAA,CAAiB,MAAM,YAAA,EAAc,KAAA,CAAM,aAAa,CAAC,CAAA;AAAA,IACnE,CAAC,iBAAA,EAAmB,KAAA,CAAM,aAAa,CAAA;AAAA,IACvC,CAAC,WAAA,EAAa,KAAA,CAAM,cAAc;AAAA,GACpC;AAEA,EAAA,MAAM,QAAQ,MAAA,CAAO,GAAA,CAAI,CAAC,CAAC,KAAK,KAAK,CAAA,KAAM,CAAA,EAAG,GAAG,IAAI,kBAAA,CAAmB,KAAK,CAAC,CAAA,CAAE,CAAA,CAAE,KAAK,GAAG,CAAA;AAE1F,EAAA,OAAO,CAAA,EAAG,KAAA,CAAM,cAAc,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA;AACzC;AAMA,SAAS,gBAAA,CAAiB,cAAsB,aAAA,EAA+B;AAC7E,EAAA,MAAM,IAAA,GAAO,cAAc,UAAA,CAAW,GAAG,IAAI,aAAA,CAAc,KAAA,CAAM,CAAC,CAAA,GAAI,aAAA;AACtE,EAAA,OAAO,CAAA,EAAG,YAAY,CAAA,EAAG,IAAI,CAAA,CAAA;AAC/B;AAEA,SAAS,qBAAA,CACP,IAAA,EACA,KAAA,EACA,IAAA,EACM;AACN,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,WAAW,CAAA,EAAG;AACnD,IAAA,MAAM,IAAI,gBAAA,CAAiB,IAAA,EAAM,CAAA,EAAA,EAAK,IAAI,CAAA,8BAAA,CAAgC,CAAA;AAAA,EAC5E;AACF;AAEA,SAAS,cAAA,CAAe,MAAc,KAAA,EAAsB;AAC1D,EAAA,qBAAA,CAAsB,IAAA,EAAM,OAAO,gBAAgB,CAAA;AACnD,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,IAAI,IAAI,KAAe,CAAA;AAAA,EAC/B,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,gBAAA,CAAiB,gBAAA,EAAkB,KAAK,IAAI,CAAA,uBAAA,EAA0B,KAAK,CAAA,CAAE,CAAA;AAAA,EACzF;AACA,EAAA,IAAI,GAAA,CAAI,QAAA,KAAa,OAAA,IAAW,GAAA,CAAI,aAAa,QAAA,EAAU;AACzD,IAAA,MAAM,IAAI,gBAAA;AAAA,MACR,gBAAA;AAAA,MACA,CAAA,EAAA,EAAK,IAAI,CAAA,gCAAA,EAAmC,KAAK,CAAA;AAAA,KACnD;AAAA,EACF;AACF;;;AC3FO,SAAS,wBAAwB,OAAA,EAA2C;AACjF,EAAA,MAAM,MAAA,GAAS,cAAc,OAAO,CAAA;AAEpC,EAAA,eAAe,UAAU,KAAA,EAAwC;AAC/D,IAAA,IAAI,CAAC,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,EAAU;AACvC,MAAA,MAAM,IAAI,gBAAA;AAAA,QACR,eAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,OAAA,GAAU,YAAA,CAAa,KAAA,CAAM,OAAO,CAAA;AAC1C,IAAA,MAAM,IAAA,GAAO,MAAM,SAAA,CAAU,MAAA,CAAO,OAAO,CAAA;AAC3C,IAAA,MAAM,GAAA,GAAM,iBAAA,CAAkB,IAAA,EAAM,MAAA,CAAO,MAAM,CAAA;AACjD,IAAA,MAAM,GAAA,GAAM,MAAM,UAAA,CAAW,OAAA,EAAS,GAAG,CAAA;AACzC,IAAA,MAAM,gBAAgB,aAAA,CAAc,MAAA,CAAO,QAAA,EAAU,KAAA,CAAM,eAAe,GAAG,CAAA;AAC7E,IAAA,OAAO,eAAA,CAAgB;AAAA,MACrB,gBAAgB,MAAA,CAAO,cAAA;AAAA,MACvB,KAAK,MAAA,CAAO,WAAA;AAAA,MACZ,GAAG,MAAA,CAAO,YAAA;AAAA,MACV,cAAc,MAAA,CAAO,YAAA;AAAA,MACrB,aAAA;AAAA,MACA,gBAAgB,MAAA,CAAO;AAAA,KACxB,CAAA;AAAA,EACH;AAEA,EAAA,eAAe,SAAS,KAAA,EAAsC;AAC5D,IAAA,MAAM,GAAA,GAAM,MAAM,SAAA,CAAU,KAAK,CAAA;AACjC,IAAA,MAAM,IAAK,UAAA,CAA4E,MAAA;AACvF,IAAA,MAAM,MAAA,GAAS,GAAG,QAAA,EAAU,MAAA;AAC5B,IAAA,IAAI,OAAO,WAAW,UAAA,EAAY;AAChC,MAAA,MAAM,IAAI,gBAAA;AAAA,QACR,wBAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAA,CAAO,IAAA,CAAK,CAAA,CAAG,QAAA,EAAU,GAAG,CAAA;AAAA,EAC9B;AAEA,EAAA,OAAO,EAAE,WAAW,QAAA,EAAS;AAC/B","file":"index.js","sourcesContent":["/**\n * Stable error codes. Merchant integrations should branch on these, not on\n * the human-readable message.\n */\nexport type ErrorCode =\n | \"INVALID_INPUT\"\n | \"INVALID_CONFIG\"\n | \"JWKS_FETCH_FAILED\"\n | \"JWKS_INVALID\"\n | \"NO_MATCHING_KEY\"\n | \"ENCRYPTION_FAILED\"\n | \"NAVIGATION_UNAVAILABLE\";\n\n/**\n * The single error type thrown by every public API in this package. Always\n * carries a stable {@link ErrorCode} on `code` and, where relevant, the\n * underlying error on `cause`.\n */\nexport class AddToKlarnaError extends Error {\n public readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"AddToKlarnaError\";\n this.code = code;\n // Restore prototype chain for ES5-targeted bundles. No-op on modern targets.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport function isAddToKlarnaError(err: unknown): err is AddToKlarnaError {\n return err instanceof AddToKlarnaError;\n}\n","/**\n * Encode a UTF-8 string as base64url, without padding. Works in both Node\n * and browsers.\n *\n * Used to wrap the JWE compact serialization into a single URL path segment\n * (without the wrap, the dots inside the JWE break path matching on Klarna's\n * web router).\n */\nexport function toBase64Url(input: string): string {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(input, \"utf8\").toString(\"base64url\");\n }\n const bytes = new TextEncoder().encode(input);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/**\n * Decode a base64url string back to UTF-8. Inverse of {@link toBase64Url}.\n * Exported mostly so tests can assert on the round-trip.\n */\nexport function fromBase64Url(input: string): string {\n const padded =\n input.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"==\".slice(0, (4 - (input.length % 4)) % 4);\n\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(padded, \"base64\").toString(\"utf8\");\n }\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder().decode(bytes);\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport { toBase64Url } from \"./utils.js\";\n\n/**\n * Compose the deep-link *path* that goes inside the outer AppsFlyer OneLink's\n * `deep_link_value` parameter:\n *\n * <linkPath>/<brandNickname>/<encryptedSegment>\n *\n * `linkPath` is a rooted path like `/loyalty-cards-v2/add-to-klarna` (see\n * {@link resolveConfig}). `encryptedSegment` is the JWE compact serialization\n * wrapped in a single base64url — the wrap is required because the dots in the\n * JWE compact form would otherwise break Klarna's in-app router path matching.\n *\n * Returns a path (not a full URL): the composed value is meant to be passed\n * verbatim to {@link buildOneLinkUrl} as `deepLinkValue`, which handles the\n * outer URL encoding.\n */\nexport function buildLinkPath(linkPath: string, brandNickname: string, jweCompact: string): string {\n if (typeof brandNickname !== \"string\" || brandNickname.length === 0) {\n throw new AddToKlarnaError(\"INVALID_INPUT\", \"`brandNickname` must be a non-empty string.\");\n }\n if (/[\\s/?#]/.test(brandNickname)) {\n throw new AddToKlarnaError(\n \"INVALID_INPUT\",\n `\\`brandNickname\\` contains invalid characters (whitespace, \"/\", \"?\" or \"#\"): ${brandNickname}`,\n );\n }\n if (typeof jweCompact !== \"string\" || jweCompact.length === 0) {\n throw new AddToKlarnaError(\"ENCRYPTION_FAILED\", \"Empty JWE returned from encryptor.\");\n }\n const encryptedPayload = toBase64Url(jweCompact);\n return `${linkPath}/${encodeURIComponent(brandNickname)}/${encryptedPayload}`;\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport type { DecryptedPayload } from \"./types.js\";\n\n/**\n * Build the plaintext payload that goes inside the JWE.\n *\n * The schema is `{ inputId, linkId, iat }`. Klarna's backend validates this\n * shape on decryption, so the field names and types must remain stable.\n */\nexport function buildPayload(inputId: string): DecryptedPayload {\n if (typeof inputId !== \"string\" || inputId.length === 0) {\n throw new AddToKlarnaError(\"INVALID_INPUT\", \"`inputId` must be a non-empty string.\");\n }\n return {\n inputId,\n linkId: globalThis.crypto.randomUUID(),\n iat: Math.floor(Date.now() / 1000),\n };\n}\n","import { AddToKlarnaError } from \"./errors.js\";\nimport type { ClientOptions, ClientTarget, Environment, Region } from \"./types.js\";\n\n/**\n * Resolved configuration used internally. Everything has been collapsed down\n * to concrete strings by the time we construct one.\n */\nexport interface ResolvedConfig {\n jwksUrl: string;\n /**\n * The in-app deep-link path prefix. Combined with the brand nickname and\n * encrypted payload by {@link buildLinkPath}, then passed as\n * `deep_link_value` on the outer AppsFlyer OneLink.\n */\n linkPath: string;\n /**\n * AppsFlyer OneLink prefix that wraps the encrypted deep link. See\n * {@link buildOneLinkUrl}.\n */\n oneLinkBaseUrl: string;\n /**\n * Custom URI scheme for the targeted Klarna app, including `://`.\n * {@link buildOneLinkUrl} appends the in-app path and sends the result as\n * `af_dp`. Selected by `clientTarget`, not by `environment`.\n */\n appUriScheme: string;\n /**\n * Desktop fallback URL (`af_web_dp`). Always the production add-to-klarna\n * page — not selected by `environment` or `clientTarget`.\n */\n desktopFallbackUrl: string;\n /** AppsFlyer media source (`pid`). */\n mediaSource: string;\n /** AppsFlyer campaign name (`c`). */\n campaignName: string;\n region: Region;\n}\n\n/**\n * JWKS endpoints per environment. **Independent of region.** Every published\n * JWKS file contains keys for every region, namespaced by a `kid-{region}-`\n * prefix; the picker selects the right one.\n */\nconst JWKS_URLS: Record<Environment, string> = {\n production: \"https://app.klarna.com/.well-known/jwks.json\",\n staging:\n \"https://eu-non-production-klarna-static-assets.s3.eu-west-1.amazonaws.com/klapp/.well-known/jwks.json\",\n};\n\n/**\n * In-app deep-link path prefixes. Combined with `<brandNickname>/<encrypted>`\n * to form the value of `deep_link_value` on the outer AppsFlyer OneLink. The\n * Klarna app parses this path and routes to the add-to-klarna screen.\n */\nconst LINK_PATHS: Record<Environment, string> = {\n production: \"/loyalty-cards-v2/add-to-klarna\",\n staging: \"/loyalty-cards-v2/add-to-klarna\",\n};\n\n/**\n * AppsFlyer OneLink prefixes. Production uses Klarna's custom `l.klarna.com`\n * short domain; staging uses the standard AppsFlyer `*.onelink.me` host that\n * targets the staging app build.\n */\nconst ONELINK_BASE_URLS: Record<ClientTarget, string> = {\n pink: \"https://l.klarna.com/22XC\",\n internalpink: \"https://klarnainternalpink.onelink.me/lXgD\",\n yellow: \"https://klarnayellow.onelink.me/JQ8X\",\n staging: \"https://klarnastaging.onelink.me/hV1K\",\n oneoff: \"https://klarnaoneoff.onelink.me/FaEr\",\n local: \"https://klarnalocal.onelink.me/dxUs\",\n};\n\n/**\n * Custom URI schemes, one per `clientTarget`. These match the schemes the\n * Klarna app registers (`klarna` plus the build suffix). {@link buildOneLinkUrl}\n * appends the in-app path before sending the result as `af_dp`.\n */\nconst APP_URI_SCHEMES: Record<ClientTarget, string> = {\n pink: \"klarna://\",\n internalpink: \"klarnainternalpink://\",\n yellow: \"klarnayellow://\",\n staging: \"klarnastaging://\",\n oneoff: \"klarnaoneoff://\",\n local: \"klarnadev://\",\n};\n\n/**\n * Desktop fallback (`af_web_dp`). One production page for every environment\n * and client target — there is no staging-specific fallback.\n */\nconst DESKTOP_FALLBACK_URL = \"https://klarna.com/add-to-klarna\";\n\n/**\n * AppsFlyer `pid` (media source).\n */\nconst MEDIA_SOURCE = \"WebApp\";\n\n/**\n * AppsFlyer `c` (campaign name).\n */\nconst CAMPAIGN_NAME = \"add-to-klarna\";\n\n/**\n * Resolve user-facing client options into a concrete config.\n *\n * Validates inputs up-front so any misconfiguration surfaces as an\n * `INVALID_CONFIG` `AddToKlarnaError` *before* the merchant page issues any\n * network calls.\n */\nexport function resolveConfig(options: ClientOptions): ResolvedConfig {\n const {\n environment = \"production\",\n clientTarget = \"pink\",\n region,\n } = options ?? ({} as ClientOptions);\n\n if (environment !== \"production\" && environment !== \"staging\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown environment: ${String(environment)}. Expected \"production\" or \"staging\".`,\n );\n }\n if (region !== \"eu\" && region !== \"us\" && region !== \"ap\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown region: ${String(region)}. Expected \"eu\", \"us\" or \"ap\".`,\n );\n }\n if (\n clientTarget !== \"pink\" &&\n clientTarget !== \"internalpink\" &&\n clientTarget !== \"yellow\" &&\n clientTarget !== \"staging\" &&\n clientTarget !== \"oneoff\" &&\n clientTarget !== \"local\"\n ) {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `Unknown clientTarget: ${String(clientTarget)}. Expected \"pink\", \"internalpink\", \"yellow\", \"staging\", \"oneoff\" or \"local\"`,\n );\n }\n\n return {\n jwksUrl: JWKS_URLS[environment],\n linkPath: LINK_PATHS[environment],\n oneLinkBaseUrl: ONELINK_BASE_URLS[clientTarget],\n appUriScheme: APP_URI_SCHEMES[clientTarget],\n desktopFallbackUrl: DESKTOP_FALLBACK_URL,\n mediaSource: MEDIA_SOURCE,\n campaignName: CAMPAIGN_NAME,\n region,\n };\n}\n","import { EncryptJWT, importJWK, type JWK } from \"jose\";\n\nimport { AddToKlarnaError } from \"./errors.js\";\nimport type { DecryptedPayload } from \"./types.js\";\n\n/**\n * The JWE profile this library produces. Pinned end-to-end:\n *\n * - `ALG` (key-encryption algorithm)\n * - `ENC` (content-encryption algorithm)\n *\n * `pickEncryptionKey` enforces the matching `kty`/`crv` on the JWKS side,\n * so a JWKS that drifts off this profile fails closed with `NO_MATCHING_KEY`\n * instead of silently producing a non-decryptable link. Changing either\n * constant is an SDK-coordinated event with Klarna's backend.\n */\nexport const ALG = \"ECDH-ES+A256KW\";\nexport const ENC = \"A256GCM\";\n\n/**\n * Encrypt the plaintext payload as a JWE using the given JWK. Produces the\n * compact serialization (five dot-separated base64url segments).\n */\nexport async function encryptJwe(\n payload: DecryptedPayload,\n key: JWK & { kid: string },\n): Promise<string> {\n let imported: Awaited<ReturnType<typeof importJWK>>;\n try {\n imported = await importJWK(key, ALG);\n } catch (cause) {\n throw new AddToKlarnaError(\n \"ENCRYPTION_FAILED\",\n `Could not import JWK with kid=\"${key.kid}\" for alg=\"${ALG}\".`,\n { cause },\n );\n }\n\n try {\n return await new EncryptJWT({\n inputId: payload.inputId,\n linkId: payload.linkId,\n })\n .setProtectedHeader({\n alg: ALG,\n enc: ENC,\n kid: key.kid,\n typ: \"JWT\",\n cty: \"application/json\",\n })\n .setIssuedAt(payload.iat)\n .encrypt(imported);\n } catch (cause) {\n throw new AddToKlarnaError(\"ENCRYPTION_FAILED\", `JWE encryption failed for kid=\"${key.kid}\".`, {\n cause,\n });\n }\n}\n","import type { JWK } from \"jose\";\n\nimport { ALG } from \"./encryptJwe.js\";\nimport { AddToKlarnaError } from \"./errors.js\";\nimport type { Region } from \"./types.js\";\n\n/**\n * The minimal JWKS shape we depend on. The actual file may contain extra\n * fields per RFC 7517; we ignore everything we do not need.\n */\nexport interface Jwks {\n keys: Array<JWK & { kid?: string; alg?: string; use?: string }>;\n}\n\n/**\n * The single key shape this library accepts off the JWKS. Pinned to the\n * tuple Klarna's backend publishes today:\n *\n * alg = ECDH-ES+A256KW\n * kty = EC\n * crv = P-256\n *\n * Anything else is rejected with `NO_MATCHING_KEY`, so a JWKS that ever\n * drifts off this profile — whether by mistake or because someone\n * managed to substitute it past TLS — fails closed instead of\n * silently using a weaker or symmetric algorithm. Any change to this\n * tuple is an SDK-coordinated event with the backend.\n */\nconst REQUIRED_KTY = \"EC\";\nconst REQUIRED_CRV = \"P-256\";\n\n/**\n * Fetch and parse the JWKS at `url`. There is no in-memory cache — caching is\n * delegated to the HTTP layer and Klarna's CDN. Wraps every failure mode in\n * a typed `AddToKlarnaError`.\n */\nexport async function fetchJwks(url: string): Promise<Jwks> {\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n });\n } catch (cause) {\n throw new AddToKlarnaError(\"JWKS_FETCH_FAILED\", `Failed to fetch JWKS from ${url}`, { cause });\n }\n\n if (!response.ok) {\n throw new AddToKlarnaError(\n \"JWKS_FETCH_FAILED\",\n `JWKS fetch returned HTTP ${response.status} ${response.statusText} for ${url}`,\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch (cause) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS response body was not valid JSON.\", { cause });\n }\n\n return assertJwks(body);\n}\n\n/**\n * Pick the encryption key for `region` from a JWKS document.\n *\n * A key is usable iff it declares all of:\n *\n * - `use === \"enc\"`\n * - `kid` starting with `kid-{region}-`\n * - `alg === ALG`, `kty === \"EC\"`, `crv === \"P-256\"` (see file header)\n *\n * If multiple keys match, the first one in document order wins — the JWKS\n * is published with the most recent key first. Throws `NO_MATCHING_KEY`\n * when nothing matches.\n */\nexport function pickEncryptionKey(jwks: Jwks, region: Region): JWK & { kid: string } {\n const prefix = `kid-${region}-`;\n const chosen = jwks.keys.find(\n (k) =>\n k.use === \"enc\" &&\n typeof k.kid === \"string\" &&\n k.kid.startsWith(prefix) &&\n k.alg === ALG &&\n k.kty === REQUIRED_KTY &&\n k.crv === REQUIRED_CRV,\n );\n\n if (!chosen || typeof chosen.kid !== \"string\") {\n throw new AddToKlarnaError(\n \"NO_MATCHING_KEY\",\n `JWKS contains no usable encryption key with kid prefix \"${prefix}\" ` +\n `(need use=\"enc\", alg=\"${ALG}\", kty=\"${REQUIRED_KTY}\", crv=\"${REQUIRED_CRV}\").`,\n );\n }\n return chosen as JWK & { kid: string };\n}\n\nfunction assertJwks(body: unknown): Jwks {\n if (!body || typeof body !== \"object\" || !(\"keys\" in body)) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS response is missing a `keys` array.\");\n }\n const keys = (body as { keys: unknown }).keys;\n if (!Array.isArray(keys)) {\n throw new AddToKlarnaError(\"JWKS_INVALID\", \"JWKS `keys` is not an array.\");\n }\n return { keys: keys as Jwks[\"keys\"] };\n}\n","import { AddToKlarnaError } from \"./errors.js\";\n\n/**\n * Inputs for {@link buildOneLinkUrl}.\n *\n * All fields are required — this function is deliberately dumb: it takes\n * concrete URLs and composes them into an AppsFlyer OneLink. Environment\n * defaults live in {@link resolveConfig}.\n */\nexport interface BuildOneLinkInput {\n /** The AppsFlyer OneLink prefix (e.g. `https://l.klarna.com/22XC`). */\n oneLinkBaseUrl: string;\n\n /** AppsFlyer media source. Sent as `pid`. */\n pid: string;\n\n /** AppsFlyer campaign name. Sent as `c`. */\n c: string;\n\n /**\n * The in-app deep link path. Sent as `deep_link_value`. AppsFlyer hands\n * this string to the app for in-app routing once the app is open, and\n * replays it after install when the app was not installed (deferred deep\n * linking). It does not launch the app.\n */\n deepLinkValue: string;\n\n /**\n * Custom URI scheme for the targeted Klarna build, including `://`\n * (for example `klarna://` or `klarnaoneoff://`). Combined with\n * `deepLinkValue` and sent as `af_dp`, so the address that launches the\n * app is also the screen.\n */\n appUriScheme: string;\n\n /** Desktop fallback URL. Sent as `af_web_dp`. */\n webFallbackUrl: string;\n}\n\n/**\n * Compose an AppsFlyer OneLink URL that:\n *\n * - launches the installed Klarna app directly to `deepLinkValue` via `af_dp`,\n * - falls back to the App Store / Play Store (configured on the OneLink\n * template, not on this URL) when the app is not installed on mobile,\n * - falls back to `webFallbackUrl` on desktop.\n *\n * The returned URL has the shape:\n *\n * <oneLinkBaseUrl>?pid=<pid>&c=<c>&af_dp=<url-encoded scheme + deepLinkValue>\n * &deep_link_value=<url-encoded deepLinkValue>\n * &af_web_dp=<url-encoded webFallbackUrl>\n *\n * `af_dp` uses two slashes (`klarnaoneoff://loyalty-cards-v2/...`). The Klarna\n * app inserts the third slash before matching the path. A bare scheme opens\n * the app and leaves it on the home screen, because that launch address has\n * no route.\n *\n * Throws `AddToKlarnaError` with `INVALID_CONFIG` if any URL or the app URI\n * scheme is missing or malformed, or `INVALID_INPUT` if `deepLinkValue` is\n * empty.\n */\nexport function buildOneLinkUrl(input: BuildOneLinkInput): string {\n requireNonEmptyString(\"oneLinkBaseUrl\", input.oneLinkBaseUrl, \"INVALID_CONFIG\");\n requireNonEmptyString(\"pid\", input.pid, \"INVALID_CONFIG\");\n requireNonEmptyString(\"c\", input.c, \"INVALID_CONFIG\");\n requireNonEmptyString(\"appUriScheme\", input.appUriScheme, \"INVALID_CONFIG\");\n requireNonEmptyString(\"deepLinkValue\", input.deepLinkValue, \"INVALID_INPUT\");\n requireHttpUrl(\"webFallbackUrl\", input.webFallbackUrl);\n\n // Insertion order is preserved by URLSearchParams / Object.entries, and it\n // matters here only for output stability (easier to snapshot in tests).\n const params: Array<[string, string]> = [\n [\"pid\", input.pid],\n [\"c\", input.c],\n [\"af_dp\", buildAppDeepLink(input.appUriScheme, input.deepLinkValue)],\n [\"deep_link_value\", input.deepLinkValue],\n [\"af_web_dp\", input.webFallbackUrl],\n ];\n\n const query = params.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(\"&\");\n\n return `${input.oneLinkBaseUrl}?${query}`;\n}\n\n/**\n * `scheme://` + path, without a leading slash on the path, so the result is\n * `klarnaoneoff://loyalty-cards-v2/add-to-klarna/...` rather than three slashes.\n */\nfunction buildAppDeepLink(appUriScheme: string, deepLinkValue: string): string {\n const path = deepLinkValue.startsWith(\"/\") ? deepLinkValue.slice(1) : deepLinkValue;\n return `${appUriScheme}${path}`;\n}\n\nfunction requireNonEmptyString(\n name: string,\n value: unknown,\n code: \"INVALID_CONFIG\" | \"INVALID_INPUT\",\n): void {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new AddToKlarnaError(code, `\\`${name}\\` must be a non-empty string.`);\n }\n}\n\nfunction requireHttpUrl(name: string, value: unknown): void {\n requireNonEmptyString(name, value, \"INVALID_CONFIG\");\n let url: URL;\n try {\n url = new URL(value as string);\n } catch {\n throw new AddToKlarnaError(\"INVALID_CONFIG\", `\\`${name}\\` is not a valid URL: ${value}`);\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new AddToKlarnaError(\n \"INVALID_CONFIG\",\n `\\`${name}\\` must use http or https. Got: ${value}`,\n );\n }\n}\n","import { buildLinkPath } from \"./buildLinkPath.js\";\nimport { buildPayload } from \"./buildPayload.js\";\nimport { resolveConfig } from \"./config.js\";\nimport { encryptJwe } from \"./encryptJwe.js\";\nimport { AddToKlarnaError } from \"./errors.js\";\nimport { fetchJwks, pickEncryptionKey } from \"./jwksClient.js\";\nimport { buildOneLinkUrl } from \"./oneLink.js\";\nimport type { AddToKlarnaClient, BuildLinkInput, ClientOptions } from \"./types.js\";\n\n/**\n * Create an Add-to-Klarna client.\n *\n * Construction is cheap and synchronous: it validates options and wires up\n * the pipeline. The first network call happens on the first\n * {@link AddToKlarnaClient.buildLink} / {@link AddToKlarnaClient.redirect}.\n *\n * Each `buildLink` returns an AppsFlyer OneLink that:\n *\n * - opens the Klarna app to the encrypted add-to-klarna deep link when the\n * app is installed,\n * - falls back to the App Store / Play Store on mobile when the app is not\n * installed (deferred deep link replays after install),\n * - falls back to `https://klarna.com/add-to-klarna` on desktop.\n *\n * Every `buildLink` re-fetches the JWKS — caching is handled by the HTTP\n * layer and Klarna's CDN, not in memory.\n */\nexport function createAddToKlarnaClient(options: ClientOptions): AddToKlarnaClient {\n const config = resolveConfig(options);\n\n async function buildLink(input: BuildLinkInput): Promise<string> {\n if (!input || typeof input !== \"object\") {\n throw new AddToKlarnaError(\n \"INVALID_INPUT\",\n \"buildLink() requires an object with `brandNickname` and `inputId`.\",\n );\n }\n const payload = buildPayload(input.inputId);\n const jwks = await fetchJwks(config.jwksUrl);\n const key = pickEncryptionKey(jwks, config.region);\n const jwe = await encryptJwe(payload, key);\n const deepLinkValue = buildLinkPath(config.linkPath, input.brandNickname, jwe);\n return buildOneLinkUrl({\n oneLinkBaseUrl: config.oneLinkBaseUrl,\n pid: config.mediaSource,\n c: config.campaignName,\n appUriScheme: config.appUriScheme,\n deepLinkValue,\n webFallbackUrl: config.desktopFallbackUrl,\n });\n }\n\n async function redirect(input: BuildLinkInput): Promise<void> {\n const url = await buildLink(input);\n const w = (globalThis as { window?: { location?: { assign?: (u: string) => void } } }).window;\n const assign = w?.location?.assign;\n if (typeof assign !== \"function\") {\n throw new AddToKlarnaError(\n \"NAVIGATION_UNAVAILABLE\",\n \"redirect() requires a browser context with `window.location.assign`. Use buildLink() and navigate yourself in non-browser environments.\",\n );\n }\n assign.call(w!.location, url);\n }\n\n return { buildLink, redirect };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klarna/add-to-klarna",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Merchant integration library for generating Klarna 'Add to Klarna' AppsFlyer OneLinks.",
|
|
5
5
|
"repository": "https://github.com/klarna-incubator/add-to-klarna-js",
|
|
6
6
|
"license": "Apache-2.0",
|