diff --git a/README.md b/README.md
index 34600a08..aec8e94e 100644
--- a/README.md
+++ b/README.md
@@ -89,6 +89,7 @@ See the list below for the name of all individual services.
| `prebid-client-server` | Prebid Client Server | 3052 | http://localhost:3052 |
| `prebid-client-side-deferred` | Prebid Client Side Deferred | 3053 | http://localhost:3053 |
| `prebid-secure-signals-client-side` | Prebid Secure Signals | 3061 | http://localhost:3061 |
+| `hashing-tool` | Hashing Tool | 3071 | http://localhost:3071 |
---
diff --git a/docker-compose.yml b/docker-compose.yml
index fd1525d6..b521d2b1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -139,4 +139,13 @@ services:
env_file:
- .env
-
+ # tools
+ hashing-tool:
+ build:
+ context: tools/hashing-tool
+ dockerfile: Dockerfile
+ ports:
+ - "3071:3071"
+ container_name: hashing-tool
+ env_file:
+ - .env
diff --git a/siteDetails.js b/siteDetails.js
index 720d2e0a..1d12870a 100644
--- a/siteDetails.js
+++ b/siteDetails.js
@@ -85,6 +85,14 @@ const sites = [
port: 3061,
description: 'Prebid Secure Signals Client Side',
},
+
+ // Tools
+ {
+ name: 'hashing-tool',
+ domain: 'hashing-tool.sample-dev.com',
+ port: 3071,
+ description: 'Hashing Tool',
+ },
];
// Export for CommonJS (used by createCA.ts)
diff --git a/tools/hashing-tool/Dockerfile b/tools/hashing-tool/Dockerfile
new file mode 100644
index 00000000..d4448bc9
--- /dev/null
+++ b/tools/hashing-tool/Dockerfile
@@ -0,0 +1,25 @@
+FROM nginx:alpine
+
+# Install gettext for envsubst
+RUN apk add --no-cache gettext
+
+# Copy static files
+COPY index.html /usr/share/nginx/html/index.html
+COPY app.css /usr/share/nginx/html/app.css
+
+# Copy entrypoint script
+COPY entrypoint.sh /entrypoint.sh
+RUN chmod +x /entrypoint.sh
+
+# Configure nginx to serve on port 3071
+RUN echo 'server { \
+ listen 3071; \
+ location / { \
+ root /usr/share/nginx/html; \
+ index index.html; \
+ } \
+}' > /etc/nginx/conf.d/default.conf
+
+EXPOSE 3071
+
+ENTRYPOINT ["/entrypoint.sh"]
diff --git a/tools/hashing-tool/README.md b/tools/hashing-tool/README.md
new file mode 100644
index 00000000..a6fd58d3
--- /dev/null
+++ b/tools/hashing-tool/README.md
@@ -0,0 +1,38 @@
+# UID2/EUID Hashing Tool
+
+A tool to verify that your implementation is normalizing and hashing email addresses and phone numbers correctly for UID2 and EUID.
+
+> **Note:** The normalization and hashing logic is identical for both UID2 and EUID.
+
+## Running Locally
+
+### Using Docker Compose
+
+From the repository root:
+
+```bash
+docker-compose up -d hashing-tool
+```
+
+Access at: http://localhost:3071
+
+### Using the Reverse Proxy (HTTPS)
+
+```bash
+docker-compose up -d
+```
+
+Access at: https://hashing-tool.sample-dev.com (requires hosts file and certificate setup — see [reverse-proxy README](../reverse-proxy/README.md))
+
+## Usage
+
+1. Select **Email** or **Phone Number**
+2. Enter the value to hash
+3. Click **Enter**
+4. View the normalized value, SHA-256 hash, and base64-encoded result
+
+## Documentation
+
+- [UID2 Normalization and Encoding](https://unifiedid.com/docs/getting-started/gs-normalization-encoding)
+- [EUID Normalization and Encoding](https://euid.eu/docs/getting-started/gs-normalization-encoding)
+
diff --git a/tools/hashing-tool/app.css b/tools/hashing-tool/app.css
index 3edd6fb4..3bd30669 100644
--- a/tools/hashing-tool/app.css
+++ b/tools/hashing-tool/app.css
@@ -1,26 +1,276 @@
+/* Color Variables */
+:root {
+ /* Brand Colors */
+ --primary-orange: #FF6B35;
+ --primary-dark: #2D3748;
+ --accent-teal: #0D9488;
+ --accent-yellow: #FBBF24;
+
+ /* Text Colors */
+ --text-dark: #1A202C;
+ --text-gray: #718096;
+
+ /* Background Colors */
+ --bg-white: #FFFFFF;
+ --bg-light: #F7FAFC;
+ --sidebar-bg: #FFF7ED;
+
+ /* Border Colors */
+ --border-color: #E2E8F0;
+
+ /* Button Colors */
+ --button-navy: rgba(2, 10, 64, 1);
+ --button-navy-hover: rgba(2, 10, 64, 0.9);
+
+ /* Link Colors */
+ --link-color: #06B6D4;
+ --link-hover: #06B6D4;
+
+ /* Alert Colors */
+ --alert-red: #f44336;
+ --alert-red-bg: rgba(244, 67, 54, 0.1);
+
+ /* Tooltip Colors */
+ --tooltip-bg: #1F2937;
+ --tooltip-trigger: #3B82F6;
+ --tooltip-trigger-hover: #2563EB;
+
+ /* Shadows */
+ --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+}
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
body {
- padding: 50px;
- font:
- 14px 'Lucida Grande',
- Helvetica,
- Arial,
- sans-serif;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
+ background: var(--bg-light);
+ color: var(--text-dark);
+ line-height: 1.6;
+ padding: 2rem;
+}
+
+/* Two Column Layout */
+.page-wrapper {
+ display: flex;
+ gap: 2rem;
+ max-width: 1400px;
+ margin: 0 auto;
+}
+
+/* Main Content Area (75%) */
+.main-content {
+ flex: 3;
+ background: var(--bg-white);
+ border-radius: 12px;
+ padding: 2.5rem;
+ box-shadow: var(--shadow-md);
+}
+
+/* Sidebar (25%) */
+.sidebar {
+ flex: 1;
+ background: var(--sidebar-bg);
+ border-radius: 12px;
+ padding: 2rem;
+ box-shadow: var(--shadow);
+ border-left: 4px solid var(--primary-orange);
+ position: sticky;
+ top: 2rem;
+ height: fit-content;
+ max-height: calc(100vh - 4rem);
+ overflow-y: auto;
+}
+
+.sidebar h3 {
+ color: var(--primary-dark);
+ font-size: 1.1rem;
+ margin-bottom: 1rem;
+ padding-bottom: 0.5rem;
+ border-bottom: 2px solid var(--primary-orange);
+}
+
+.sidebar .section {
+ margin-bottom: 1.5rem;
+}
+
+.sidebar .section h4 {
+ color: var(--accent-teal);
+ font-size: 0.9rem;
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+}
+
+.sidebar ul {
+ margin-left: 1.25rem;
+ font-size: 0.875rem;
+ color: var(--text-gray);
+}
+
+.sidebar li {
+ margin-bottom: 0.5rem;
+ line-height: 1.6;
+}
+
+.sidebar .note {
+ background: rgba(255, 107, 53, 0.1);
+ padding: 0.75rem;
+ border-radius: 6px;
+ font-size: 0.85rem;
+ color: var(--text-dark);
+ margin-top: 1rem;
+}
+
+.sidebar .note strong {
+ color: var(--primary-orange);
+}
+
+/* Header */
+h1 {
+ font-size: 2rem;
+ font-weight: 800;
+ color: var(--primary-dark);
+ margin-bottom: 0.75rem;
+ line-height: 1.3;
+}
+
+h2 {
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: var(--primary-dark);
+ margin: 1.5rem 0 1rem 0;
+ padding-bottom: 0.5rem;
+ border-bottom: 3px solid var(--primary-orange);
+}
+
+.intro {
+ font-size: 0.95rem;
+ color: var(--text-dark);
+ margin-bottom: 1.5rem;
+ line-height: 1.8;
}
a {
- color: #00b7ff;
+ color: var(--link-color);
+ text-decoration: underline;
+ font-weight: 500;
+ transition: opacity 0.2s ease;
}
-.alert {
- padding: 20px;
- background-color: #f44336;
+a:hover {
+ opacity: 0.8;
+}
+
+/* Forms */
+.form {
+ margin-top: 2rem;
+}
+
+.form.top-form {
+ margin-top: 0;
+ margin-bottom: 1.5rem;
+}
+
+/* Input Type Toggle (Email/Phone) */
+.input-type-toggle {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 1rem;
+}
+
+.input-type-toggle input[type="radio"] {
+ width: 16px;
+ height: 16px;
+ accent-color: var(--button-navy);
+ margin: 0;
+}
+
+.input-type-toggle label {
+ margin-right: 1.5rem;
+ font-weight: 500;
+ color: var(--text-dark);
+ cursor: pointer;
+}
+
+.email_prompt {
+ display: flex;
+ gap: 0;
+ max-width: 600px;
+ box-shadow: var(--shadow);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+#input_value {
+ flex: 1;
+ padding: 0.875rem 1.25rem;
+ border: 2px solid var(--border-color);
+ border-right: none;
+ font-size: 0.95rem;
+ color: var(--text-dark);
+ outline: none;
+ transition: border-color 0.2s ease;
+}
+
+#input_value:focus {
+ border-color: var(--primary-orange);
+}
+
+#input_value::placeholder {
+ color: var(--text-gray);
+}
+
+/* Buttons */
+.button {
+ padding: 0.875rem 2rem;
+ background: var(--button-navy);
color: white;
- width: 400px;
+ border: none;
+ font-size: 0.95rem;
+ font-weight: 700;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ white-space: nowrap;
+ box-shadow: var(--shadow);
+}
+
+.button:hover {
+ background: var(--button-navy-hover);
+ transform: translateY(-2px);
+ box-shadow: 0 6px 12px rgba(2, 10, 64, 0.3);
+}
+
+.button:active {
+ transform: translateY(0);
+}
+
+/* Clear button (full width like other sample sites) */
+#clear_form .button {
+ width: 100%;
+ max-width: 600px;
+ border-radius: 8px;
+}
+
+/* Alert Messages */
+.alert {
+ padding: 1rem 1.25rem;
+ background-color: var(--alert-red-bg);
+ border-left: 4px solid var(--alert-red);
+ color: var(--alert-red);
+ border-radius: 6px;
+ margin: 1rem 0;
+ max-width: 600px;
+ display: none;
}
.closebtn {
margin-left: 15px;
- color: white;
+ color: var(--alert-red);
font-weight: bold;
float: right;
font-size: 22px;
@@ -30,81 +280,159 @@ a {
}
.closebtn:hover {
- color: black;
+ opacity: 0.7;
}
-.button {
- border-style: none;
- cursor: pointer;
- align-items: center;
- height: 40px;
- width: 401px;
- text-align: center;
- position: absolute;
- letter-spacing: 0.28px;
- box-sizing: border-box;
- color: white;
- font-family: 'Raleway', Helvetica, Arial, serif;
- font-size: 14px;
- font-style: normal;
- font-weight: 700;
- text-transform: none;
- text-indent: 0;
- text-shadow: none;
- margin: 0;
- padding: 1px 6px;
- background-color: rgba(2, 10, 64);
- border-image: initial;
+/* Results Table */
+#results_table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 1rem 0;
+ font-size: 0.875rem;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ overflow: visible;
}
-.form {
- margin-top: 40px;
-
+#results_table tr {
+ border-bottom: 1px solid var(--border-color);
}
-.prompt {
- align-items: center;
- align-self: center;
- background-color: white;
- border: 1px solid rgba(2, 10, 64);
- border-radius: 2px;
- box-sizing: border-box;
+#results_table tr:nth-child(even) {
+ background-color: var(--bg-light);
+}
+
+#results_table tr:last-child {
+ border-bottom: none;
+}
+
+#results_table td {
+ padding: 1rem;
+ vertical-align: top;
+}
+
+#results_table .label {
+ font-weight: 600;
+ color: var(--text-dark);
+ white-space: nowrap;
+ padding-right: 2rem;
+ width: 15em;
+}
+
+#results_table .value {
+ color: var(--text-gray);
+ font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
+}
+
+#results_table .value pre {
+ white-space: pre-wrap;
+ word-break: break-all;
+ margin: 0;
+}
+
+/* Tooltip Styles */
+.tooltip-wrapper {
display: inline-flex;
- flex-direction: row;
- flex-shrink: 0;
- height: 40px;
- justify-content: flex-start;
- margin-right: 1px;
- margin-bottom: 20px;
- min-width: 399px;
- padding: 0 16px;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.tooltip {
position: relative;
- width: auto;
+ display: inline-flex;
+ align-items: center;
+ cursor: help;
}
-#input_value {
- background-color: white;
- border-style: none;
+.tooltip-trigger {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background-color: var(--tooltip-trigger);
+ color: white;
+ border: none;
+ font-size: 0.7rem;
+ font-weight: 700;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: help;
+ transition: all 0.2s ease;
flex-shrink: 0;
- height: auto;
- letter-spacing: 0.12px;
- line-height: 16px;
- min-height: 16px;
- position: relative;
- text-align: left;
- white-space: nowrap;
- width: 351px;
- color: rgba(2, 10, 64, 1);
- font-family: 'Raleway', Helvetica, Arial, serif;
- font-size: 12px;
- font-style: normal;
- font-weight: 500;
- padding: 1px 2px;
- outline: none;
}
-h1 {
- padding-bottom: 20px;
+.tooltip-trigger:hover {
+ background-color: var(--tooltip-trigger-hover);
+ transform: scale(1.05);
+}
+
+.tooltip-content {
+ visibility: hidden;
+ opacity: 0;
+ position: absolute;
+ bottom: calc(100% + 8px);
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: var(--tooltip-bg);
+ color: white;
+ padding: 10px;
+ border-radius: 4px;
+ font-size: 0.75rem;
+ line-height: 1.4;
+ min-width: 250px;
+ max-width: 350px;
+ white-space: normal;
+ z-index: 10000;
+ box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
+ transition: opacity 0.2s ease, visibility 0.2s ease;
+ font-weight: 400;
+ pointer-events: none;
+}
+
+.tooltip-content::after {
+ content: '';
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ border-width: 6px;
+ border-style: solid;
+ border-color: var(--tooltip-bg) transparent transparent transparent;
}
+.tooltip:hover .tooltip-content {
+ visibility: visible;
+ opacity: 1;
+}
+@media (max-width: 1024px) {
+ .page-wrapper {
+ flex-direction: column;
+ }
+
+ .sidebar {
+ position: static;
+ max-height: none;
+ }
+
+ body {
+ padding: 1rem;
+ }
+
+ .main-content {
+ padding: 1.5rem;
+ }
+
+ .email_prompt {
+ flex-direction: column;
+ }
+
+ #input_value {
+ border-right: 2px solid var(--border-color);
+ border-bottom: none;
+ }
+
+ .button {
+ width: 100%;
+ }
+}
diff --git a/tools/hashing-tool/entrypoint.sh b/tools/hashing-tool/entrypoint.sh
new file mode 100644
index 00000000..718567e2
--- /dev/null
+++ b/tools/hashing-tool/entrypoint.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Set default values if not provided
+export UID_JS_SDK_URL=${UID_JS_SDK_URL:-"https://cdn.integ.uidapi.com/uid2-sdk-4.0.1.js"}
+export UID_JS_SDK_NAME=${UID_JS_SDK_NAME:-"__uid2"}
+export IDENTITY_NAME=${IDENTITY_NAME:-"UID2"}
+export DOCS_BASE_URL=${DOCS_BASE_URL:-"https://unifiedid.com/docs"}
+
+# Process index.html template with environment variables
+envsubst < /usr/share/nginx/html/index.html > /usr/share/nginx/html/index.temp.html
+mv /usr/share/nginx/html/index.temp.html /usr/share/nginx/html/index.html
+
+# Start nginx
+exec nginx -g "daemon off;"
+
diff --git a/tools/hashing-tool/index.html b/tools/hashing-tool/index.html
index e40ba706..419e2171 100644
--- a/tools/hashing-tool/index.html
+++ b/tools/hashing-tool/index.html
@@ -2,23 +2,31 @@
- UID2 Hashing Tool
+ ${IDENTITY_NAME} Hashing Tool
-
-
+
- UID2 Hashing Tool
-
- Use this tool to verify that your own implementation is normalizing and
- encoding correctly. Choose Email or Phone Number, then type or paste the
- value and click Enter.
- NOTE: Normalize phone numbers before using the tool.
- For details and examples, see Normalization and Encoding .
-
-
-
- Email
-
- Phone Number
-
-
-
-
-
+
+
+
diff --git a/tools/hashing-tool/uid2-sdk-3.3.0.js b/tools/hashing-tool/uid2-sdk-3.3.0.js
deleted file mode 100644
index cd772691..00000000
--- a/tools/hashing-tool/uid2-sdk-3.3.0.js
+++ /dev/null
@@ -1,1520 +0,0 @@
-/******/ (() => { // webpackBootstrap
-/******/ "use strict";
-/******/ var __webpack_modules__ = ({
-
-/***/ 531:
-/***/ ((__unused_webpack_module, exports) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.isOptoutIdentity = exports.isValidIdentity = void 0;
-function isValidIdentity(identity) {
- return (typeof identity === 'object' &&
- identity !== null &&
- 'advertising_token' in identity &&
- 'identity_expires' in identity &&
- 'refresh_from' in identity &&
- 'refresh_token' in identity &&
- 'refresh_expires' in identity);
-}
-exports.isValidIdentity = isValidIdentity;
-function isOptoutIdentity(identity) {
- if (identity === null || typeof identity !== 'object')
- return false;
- const maybeIdentity = identity;
- return maybeIdentity.status === 'optout';
-}
-exports.isOptoutIdentity = isOptoutIdentity;
-
-
-/***/ }),
-
-/***/ 367:
-/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
-
-
-var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
-};
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.ApiClient = void 0;
-const sdkBase_1 = __webpack_require__(533);
-const Identity_1 = __webpack_require__(531);
-const cstgBox_1 = __webpack_require__(828);
-const cstgCrypto_1 = __webpack_require__(135);
-const clientSideIdentityOptions_1 = __webpack_require__(522);
-const base64_1 = __webpack_require__(819);
-function isValidRefreshResponse(response) {
- if (isUnvalidatedRefreshResponse(response)) {
- return (response.status === 'optout' ||
- response.status === 'expired_token' ||
- (response.status === 'success' && 'body' in response && (0, Identity_1.isValidIdentity)(response.body)));
- }
- return false;
-}
-function isUnvalidatedRefreshResponse(response) {
- return typeof response === 'object' && response !== null && 'status' in response;
-}
-function isCstgApiSuccessResponse(response) {
- if (response === null || typeof response !== 'object') {
- return false;
- }
- const successResponse = response;
- return successResponse.status === 'success' && (0, Identity_1.isValidIdentity)(successResponse.body);
-}
-function isCstgApiOptoutResponse(response) {
- if (response === null || typeof response !== 'object') {
- return false;
- }
- const optoutResponse = response;
- return optoutResponse.status === 'optout';
-}
-function isCstgApiClientErrorResponse(response) {
- if (response === null || typeof response !== 'object') {
- return false;
- }
- const errorResponse = response;
- return errorResponse.status === 'client_error' && typeof errorResponse.message === 'string';
-}
-function isCstgApiForbiddenResponse(response) {
- if (response === null || typeof response !== 'object') {
- return false;
- }
- const forbiddenResponse = response;
- return (forbiddenResponse.status === 'invalid_http_origin' &&
- typeof forbiddenResponse.message === 'string');
-}
-class ApiClient {
- constructor(opts, defaultBaseUrl, productName) {
- var _a;
- this._requestsInFlight = [];
- this._baseUrl = (_a = opts.baseUrl) !== null && _a !== void 0 ? _a : defaultBaseUrl;
- this._productName = productName;
- this._clientVersion = productName.toLowerCase() + '-sdk-' + sdkBase_1.SdkBase.VERSION;
- }
- hasActiveRequests() {
- return this._requestsInFlight.length > 0;
- }
- ResponseToRefreshResult(response) {
- if (isValidRefreshResponse(response)) {
- if (response.status === 'success')
- return { status: response.status, identity: response.body };
- return response;
- }
- else
- return "Response didn't contain a valid status";
- }
- abortActiveRequests() {
- this._requestsInFlight.forEach((req) => {
- req.abort();
- });
- this._requestsInFlight = [];
- }
- callRefreshApi(refreshDetails) {
- const url = this._baseUrl + '/v2/token/refresh';
- const req = new XMLHttpRequest();
- this._requestsInFlight.push(req);
- req.overrideMimeType('text/plain');
- req.open('POST', url, true);
- req.setRequestHeader('X-UID2-Client-Version', this._clientVersion); // N.B. EUID and UID2 currently both use the same header
- let resolvePromise;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- let rejectPromise;
- const promise = new Promise((resolve, reject) => {
- resolvePromise = resolve;
- rejectPromise = reject;
- });
- req.onreadystatechange = () => {
- if (req.readyState !== req.DONE)
- return;
- this._requestsInFlight = this._requestsInFlight.filter((r) => r !== req);
- try {
- if (!refreshDetails.refresh_response_key || req.status !== 200) {
- const response = JSON.parse(req.responseText);
- const result = this.ResponseToRefreshResult(response);
- if (typeof result === 'string')
- rejectPromise(result);
- else
- resolvePromise(result);
- }
- else {
- const encodeResp = (0, base64_1.base64ToBytes)(req.responseText);
- window.crypto.subtle
- .importKey('raw', (0, base64_1.base64ToBytes)(refreshDetails.refresh_response_key), { name: 'AES-GCM' }, false, ['decrypt'])
- .then((key) => {
- //returns the symmetric key
- window.crypto.subtle
- .decrypt({
- name: 'AES-GCM',
- iv: encodeResp.slice(0, 12),
- tagLength: 128, //The tagLength you used to encrypt (if any)
- }, key, encodeResp.slice(12))
- .then((decrypted) => {
- const decryptedResponse = String.fromCharCode(...new Uint8Array(decrypted));
- const response = JSON.parse(decryptedResponse);
- const result = this.ResponseToRefreshResult(response);
- if (typeof result === 'string')
- rejectPromise(result);
- else
- resolvePromise(result);
- }, (reason) => rejectPromise(`Call to ${this._productName} API failed: ` + reason));
- }, (reason) => rejectPromise(`Call to ${this._productName} API failed: ` + reason));
- }
- }
- catch (err) {
- rejectPromise(err);
- }
- };
- req.send(refreshDetails.refresh_token);
- return promise;
- }
- callCstgApi(data, opts) {
- return __awaiter(this, void 0, void 0, function* () {
- const optoutPayload = this._productName == 'EUID' ? { optout_check: 1 } : {};
- const request = 'emailHash' in data
- ? Object.assign({ email_hash: data.emailHash }, optoutPayload) : Object.assign({ phone_hash: data.phoneHash }, optoutPayload);
- const box = yield cstgBox_1.CstgBox.build((0, clientSideIdentityOptions_1.stripPublicKeyPrefix)(opts.serverPublicKey));
- const encoder = new TextEncoder();
- const now = Date.now();
- const { iv, ciphertext } = yield box.encrypt(encoder.encode(JSON.stringify(request)), encoder.encode(JSON.stringify([now])));
- const exportedPublicKey = yield (0, cstgCrypto_1.exportPublicKey)(box.clientPublicKey);
- const requestBody = {
- payload: (0, base64_1.bytesToBase64)(new Uint8Array(ciphertext)),
- iv: (0, base64_1.bytesToBase64)(new Uint8Array(iv)),
- public_key: (0, base64_1.bytesToBase64)(new Uint8Array(exportedPublicKey)),
- timestamp: now,
- subscription_id: opts.subscriptionId,
- };
- const url = this._baseUrl + '/v2/token/client-generate';
- const req = new XMLHttpRequest();
- this._requestsInFlight.push(req);
- req.overrideMimeType('text/plain');
- req.open('POST', url, true);
- let resolvePromise;
- let rejectPromise;
- const promise = new Promise((resolve, reject) => {
- resolvePromise = resolve;
- rejectPromise = reject;
- });
- req.onreadystatechange = () => __awaiter(this, void 0, void 0, function* () {
- if (req.readyState !== req.DONE)
- return;
- this._requestsInFlight = this._requestsInFlight.filter((r) => r !== req);
- try {
- if (req.status === 200) {
- const encodedResp = (0, base64_1.base64ToBytes)(req.responseText);
- const decrypted = yield box.decrypt(encodedResp.slice(0, 12), encodedResp.slice(12));
- const decryptedResponse = new TextDecoder().decode(decrypted);
- const response = JSON.parse(decryptedResponse);
- if (isCstgApiSuccessResponse(response)) {
- resolvePromise({
- status: 'success',
- identity: response.body,
- });
- }
- else if (isCstgApiOptoutResponse(response)) {
- resolvePromise({
- status: 'optout',
- });
- }
- else {
- // A 200 should always be a success response.
- // Something has gone wrong.
- rejectPromise(`API error: Response body was invalid for HTTP status 200: ${decryptedResponse}`);
- }
- }
- else if (req.status === 400) {
- const response = JSON.parse(req.responseText);
- if (isCstgApiClientErrorResponse(response)) {
- rejectPromise(`Client error: ${response.message}`);
- }
- else {
- // A 400 should always be a client error.
- // Something has gone wrong.
- rejectPromise(`API error: Response body was invalid for HTTP status 400: ${req.responseText}`);
- }
- }
- else if (req.status === 403) {
- const response = JSON.parse(req.responseText);
- if (isCstgApiForbiddenResponse(response)) {
- rejectPromise(`Forbidden: ${response.message}`);
- }
- else {
- // A 403 should always be a forbidden response.
- // Something has gone wrong.
- rejectPromise(`API error: Response body was invalid for HTTP status 403: ${req.responseText}`);
- }
- }
- else {
- rejectPromise(`API error: Unexpected HTTP status ${req.status}`);
- }
- }
- catch (err) {
- rejectPromise(err);
- }
- });
- req.send(JSON.stringify(requestBody));
- return yield promise;
- });
- }
-}
-exports.ApiClient = ApiClient;
-
-
-/***/ }),
-
-/***/ 230:
-/***/ ((__unused_webpack_module, exports) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.CallbackManager = exports.EventType = void 0;
-var EventType;
-(function (EventType) {
- EventType["InitCompleted"] = "InitCompleted";
- EventType["IdentityUpdated"] = "IdentityUpdated";
- EventType["SdkLoaded"] = "SdkLoaded";
- EventType["OptoutReceived"] = "OptoutReceived";
-})(EventType = exports.EventType || (exports.EventType = {}));
-class CallbackManager {
- constructor(sdk, productName, getIdentity, logger) {
- this._sentInit = false;
- this._productName = productName;
- this._logger = logger;
- this._getIdentity = getIdentity;
- this._sdk = sdk;
- this._sdk.callbacks.push = this.callbackPushInterceptor.bind(this);
- }
- callbackPushInterceptor(...args) {
- var _a;
- for (const c of args) {
- if (CallbackManager._sentSdkLoaded[this._productName])
- this.safeRunCallback(c, EventType.SdkLoaded, {});
- if (this._sentInit)
- this.safeRunCallback(c, EventType.InitCompleted, {
- identity: (_a = this._getIdentity()) !== null && _a !== void 0 ? _a : null,
- });
- }
- return Array.prototype.push.apply(this._sdk.callbacks, args);
- }
- runCallbacks(event, payload) {
- var _a;
- if (event === EventType.InitCompleted)
- this._sentInit = true;
- if (event === EventType.SdkLoaded)
- CallbackManager._sentSdkLoaded[this._productName] = true;
- if (!this._sentInit && event !== EventType.SdkLoaded)
- return;
- const enrichedPayload = Object.assign(Object.assign({}, payload), { identity: (_a = this._getIdentity()) !== null && _a !== void 0 ? _a : null });
- for (const callback of this._sdk.callbacks) {
- this.safeRunCallback(callback, event, enrichedPayload);
- }
- }
- safeRunCallback(callback, event, payload) {
- if (typeof callback === 'function') {
- try {
- callback(event, payload);
- }
- catch (exception) {
- this._logger.warn('SDK callback threw an exception', exception);
- }
- }
- else {
- this._logger.warn("An SDK callback was supplied which isn't a function.");
- }
- }
-}
-exports.CallbackManager = CallbackManager;
-CallbackManager._sentSdkLoaded = {};
-
-
-/***/ }),
-
-/***/ 522:
-/***/ ((__unused_webpack_module, exports) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.isClientSideIdentityOptionsOrThrow = exports.stripPublicKeyPrefix = void 0;
-const SERVER_PUBLIC_KEY_PREFIX_LENGTH = 9;
-function stripPublicKeyPrefix(serverPublicKey) {
- return serverPublicKey.substring(SERVER_PUBLIC_KEY_PREFIX_LENGTH);
-}
-exports.stripPublicKeyPrefix = stripPublicKeyPrefix;
-function isClientSideIdentityOptionsOrThrow(maybeOpts) {
- if (typeof maybeOpts !== 'object' || maybeOpts === null) {
- throw new TypeError('opts must be an object');
- }
- const opts = maybeOpts;
- if (typeof opts.serverPublicKey !== 'string') {
- throw new TypeError('opts.serverPublicKey must be a string');
- }
- const serverPublicKeyPrefix = /^UID2-X-[A-Z]-.+/;
- if (!serverPublicKeyPrefix.test(opts.serverPublicKey)) {
- throw new TypeError(`opts.serverPublicKey must match the regular expression ${serverPublicKeyPrefix}`);
- }
- // We don't do any further validation of the public key, as we will find out
- // later if it's valid by using importKey.
- if (typeof opts.subscriptionId !== 'string') {
- throw new TypeError('opts.subscriptionId must be a string');
- }
- if (opts.subscriptionId.length === 0) {
- throw new TypeError('opts.subscriptionId is empty');
- }
- return true;
-}
-exports.isClientSideIdentityOptionsOrThrow = isClientSideIdentityOptionsOrThrow;
-
-
-/***/ }),
-
-/***/ 852:
-/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.CookieManager = exports.isLegacyCookie = void 0;
-const Identity_1 = __webpack_require__(531);
-function isLegacyCookie(cookie) {
- if (typeof cookie !== 'object' || !cookie)
- return false;
- const partialCookie = cookie;
- if ('advertising_token' in partialCookie &&
- 'refresh_token' in partialCookie &&
- partialCookie.advertising_token &&
- partialCookie.refresh_token)
- return true;
- return false;
-}
-exports.isLegacyCookie = isLegacyCookie;
-function enrichIdentity(identity, now) {
- return Object.assign({ refresh_from: now, refresh_expires: now + 7 * 86400 * 1000, identity_expires: now + 4 * 3600 * 1000 }, identity);
-}
-class CookieManager {
- constructor(opts, cookieName) {
- this._cookieName = cookieName;
- this._opts = opts;
- }
- setCookie(identity) {
- var _a;
- const value = JSON.stringify(identity);
- const expires = new Date(identity.refresh_expires);
- const path = (_a = this._opts.cookiePath) !== null && _a !== void 0 ? _a : '/';
- let cookie = this._cookieName +
- '=' +
- encodeURIComponent(value) +
- ' ;path=' +
- path +
- ';expires=' +
- expires.toUTCString();
- if (typeof this._opts.cookieDomain !== 'undefined') {
- cookie += ';domain=' + this._opts.cookieDomain;
- }
- document.cookie = cookie;
- }
- removeCookie() {
- document.cookie = this._cookieName + '=;expires=Tue, 1 Jan 1980 23:59:59 GMT';
- }
- getCookie() {
- const docCookie = document.cookie;
- if (docCookie) {
- const payload = docCookie.split('; ').find((row) => row.startsWith(this._cookieName + '='));
- if (payload) {
- return decodeURIComponent(payload.split('=')[1]);
- }
- }
- }
- migrateLegacyCookie(identity, now) {
- const newCookie = enrichIdentity(identity, now);
- this.setCookie(newCookie);
- return newCookie;
- }
- loadIdentityFromCookie() {
- const payload = this.getCookie();
- if (payload) {
- const result = JSON.parse(payload);
- if ((0, Identity_1.isValidIdentity)(result))
- return result;
- if ((0, Identity_1.isOptoutIdentity)(result))
- return result;
- if (isLegacyCookie(result)) {
- return this.migrateLegacyCookie(result, Date.now());
- }
- }
- return null;
- }
-}
-exports.CookieManager = CookieManager;
-
-
-/***/ }),
-
-/***/ 828:
-/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
-
-
-var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
-};
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.CstgBox = void 0;
-const cstgCrypto_1 = __webpack_require__(135);
-class CstgBox {
- constructor(clientPublicKey, sharedKey) {
- this._clientPublicKey = clientPublicKey;
- this._sharedKey = sharedKey;
- }
- static build(serverPublicKey) {
- return __awaiter(this, void 0, void 0, function* () {
- const clientKeyPair = yield (0, cstgCrypto_1.generateKeyPair)(CstgBox._namedCurve);
- const importedServerPublicKey = yield (0, cstgCrypto_1.importPublicKey)(serverPublicKey, this._namedCurve);
- const sharedKey = yield (0, cstgCrypto_1.deriveKey)(importedServerPublicKey, clientKeyPair.privateKey);
- return new CstgBox(clientKeyPair.publicKey, sharedKey);
- });
- }
- encrypt(plaintext, additionalData) {
- return __awaiter(this, void 0, void 0, function* () {
- const iv = window.crypto.getRandomValues(new Uint8Array(12));
- const ciphertext = yield (0, cstgCrypto_1.encrypt)(plaintext, this._sharedKey, iv, additionalData);
- return {
- iv: iv,
- ciphertext: ciphertext,
- };
- });
- }
- decrypt(iv, ciphertext) {
- return __awaiter(this, void 0, void 0, function* () {
- return yield (0, cstgCrypto_1.decrypt)(ciphertext, this._sharedKey, iv);
- });
- }
- get clientPublicKey() {
- return this._clientPublicKey;
- }
-}
-exports.CstgBox = CstgBox;
-CstgBox._namedCurve = 'P-256';
-
-
-/***/ }),
-
-/***/ 135:
-/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.decrypt = exports.encrypt = exports.deriveKey = exports.exportPublicKey = exports.importPublicKey = exports.generateKeyPair = void 0;
-const base64_1 = __webpack_require__(819);
-function generateKeyPair(namedCurve) {
- const params = {
- name: 'ECDH',
- namedCurve: namedCurve,
- };
- return window.crypto.subtle.generateKey(params, false, ['deriveKey']);
-}
-exports.generateKeyPair = generateKeyPair;
-function importPublicKey(publicKey, namedCurve) {
- const params = {
- name: 'ECDH',
- namedCurve: namedCurve,
- };
- return window.crypto.subtle.importKey('spki', (0, base64_1.base64ToBytes)(publicKey), params, false, []);
-}
-exports.importPublicKey = importPublicKey;
-function exportPublicKey(publicKey) {
- return window.crypto.subtle.exportKey('spki', publicKey);
-}
-exports.exportPublicKey = exportPublicKey;
-function deriveKey(serverPublicKey, clientPrivateKey) {
- return window.crypto.subtle.deriveKey({
- name: 'ECDH',
- public: serverPublicKey,
- }, clientPrivateKey, {
- name: 'AES-GCM',
- length: 256,
- }, false, ['encrypt', 'decrypt']);
-}
-exports.deriveKey = deriveKey;
-function encrypt(data, key, iv, additionalData) {
- return window.crypto.subtle.encrypt({
- name: 'AES-GCM',
- iv: iv,
- additionalData: additionalData,
- }, key, data);
-}
-exports.encrypt = encrypt;
-function decrypt(data, key, iv) {
- return window.crypto.subtle.decrypt({
- name: 'AES-GCM',
- iv: iv,
- }, key, data);
-}
-exports.decrypt = decrypt;
-
-
-/***/ }),
-
-/***/ 838:
-/***/ ((__unused_webpack_module, exports) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.normalizeEmail = exports.isNormalizedPhone = void 0;
-function isNormalizedPhone(phone) {
- return /^\+[0-9]{10,15}$/.test(phone);
-}
-exports.isNormalizedPhone = isNormalizedPhone;
-const EMAIL_EXTENSION_SYMBOL = '+';
-const EMAIL_DOT = '.';
-const GMAIL_DOMAIN = 'gmail.com';
-function splitEmailIntoAddressAndDomain(email) {
- const parts = email.split('@');
- if (!parts.length || parts.length !== 2 || parts.some((part) => part === ''))
- return;
- return {
- address: parts[0],
- domain: parts[1],
- };
-}
-function isGmail(domain) {
- return domain === GMAIL_DOMAIN;
-}
-function dropExtension(address, extensionSymbol = EMAIL_EXTENSION_SYMBOL) {
- return address.split(extensionSymbol)[0];
-}
-function normalizeAddressPart(address, shouldRemoveDot, shouldDropExtension) {
- let parsedAddress = address;
- if (shouldRemoveDot)
- parsedAddress = parsedAddress.replaceAll(EMAIL_DOT, '');
- if (shouldDropExtension)
- parsedAddress = dropExtension(parsedAddress);
- return parsedAddress;
-}
-function normalizeEmail(email) {
- if (!email || !email.length)
- return;
- const parsedEmail = email.trim().toLowerCase();
- if (parsedEmail.indexOf(' ') > 0)
- return;
- const emailParts = splitEmailIntoAddressAndDomain(parsedEmail);
- if (!emailParts)
- return;
- const { address, domain } = emailParts;
- const emailIsGmail = isGmail(domain);
- const parsedAddress = normalizeAddressPart(address, emailIsGmail, emailIsGmail);
- return parsedAddress ? `${parsedAddress}@${domain}` : undefined;
-}
-exports.normalizeEmail = normalizeEmail;
-
-
-/***/ }),
-
-/***/ 819:
-/***/ ((__unused_webpack_module, exports) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.bytesToBase64 = exports.base64ToBytes = void 0;
-function base64ToBytes(base64) {
- const binString = atob(base64);
- return Uint8Array.from(binString, (m) => m.codePointAt(0));
-}
-exports.base64ToBytes = base64ToBytes;
-function bytesToBase64(bytes) {
- const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join('');
- return btoa(binString);
-}
-exports.bytesToBase64 = bytesToBase64;
-
-
-/***/ }),
-
-/***/ 699:
-/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
-
-
-var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
-};
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.hashIdentifier = exports.hashAndEncodeIdentifier = void 0;
-const base64_1 = __webpack_require__(819);
-function hashAndEncodeIdentifier(value) {
- return __awaiter(this, void 0, void 0, function* () {
- const hash = yield window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));
- return (0, base64_1.bytesToBase64)(new Uint8Array(hash));
- });
-}
-exports.hashAndEncodeIdentifier = hashAndEncodeIdentifier;
-function hashIdentifier(value) {
- return __awaiter(this, void 0, void 0, function* () {
- const hash = yield window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));
- // converting 32-byte SHA-256 to hex-encoded representation
- return [...new Uint8Array(hash)].map((x) => x.toString(16).padStart(2, '0')).join('');
- });
-}
-exports.hashIdentifier = hashIdentifier;
-
-
-/***/ }),
-
-/***/ 479:
-/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.SdkBase = exports.hashAndEncodeIdentifier = exports.isBase64Hash = exports.isNormalizedPhone = exports.isClientSideIdentityOptionsOrThrow = exports.EventType = void 0;
-var callbackManager_1 = __webpack_require__(230);
-Object.defineProperty(exports, "EventType", ({ enumerable: true, get: function () { return callbackManager_1.EventType; } }));
-var clientSideIdentityOptions_1 = __webpack_require__(522);
-Object.defineProperty(exports, "isClientSideIdentityOptionsOrThrow", ({ enumerable: true, get: function () { return clientSideIdentityOptions_1.isClientSideIdentityOptionsOrThrow; } }));
-var diiNormalization_1 = __webpack_require__(838);
-Object.defineProperty(exports, "isNormalizedPhone", ({ enumerable: true, get: function () { return diiNormalization_1.isNormalizedPhone; } }));
-var hashedDii_1 = __webpack_require__(254);
-Object.defineProperty(exports, "isBase64Hash", ({ enumerable: true, get: function () { return hashedDii_1.isBase64Hash; } }));
-var hash_1 = __webpack_require__(699);
-Object.defineProperty(exports, "hashAndEncodeIdentifier", ({ enumerable: true, get: function () { return hash_1.hashAndEncodeIdentifier; } }));
-var sdkBase_1 = __webpack_require__(533);
-Object.defineProperty(exports, "SdkBase", ({ enumerable: true, get: function () { return sdkBase_1.SdkBase; } }));
-
-
-/***/ }),
-
-/***/ 254:
-/***/ ((__unused_webpack_module, exports) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.isBase64Hash = void 0;
-function isBase64Hash(value) {
- if (!(value && value.length === 44)) {
- return false;
- }
- try {
- return btoa(atob(value)) === value;
- }
- catch (err) {
- return false;
- }
-}
-exports.isBase64Hash = isBase64Hash;
-
-
-/***/ }),
-
-/***/ 755:
-/***/ ((__unused_webpack_module, exports) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.notifyInitCallback = exports.IdentityStatus = void 0;
-var IdentityStatus;
-(function (IdentityStatus) {
- IdentityStatus[IdentityStatus["ESTABLISHED"] = 0] = "ESTABLISHED";
- IdentityStatus[IdentityStatus["REFRESHED"] = 1] = "REFRESHED";
- IdentityStatus[IdentityStatus["EXPIRED"] = 100] = "EXPIRED";
- IdentityStatus[IdentityStatus["NO_IDENTITY"] = -1] = "NO_IDENTITY";
- IdentityStatus[IdentityStatus["INVALID"] = -2] = "INVALID";
- IdentityStatus[IdentityStatus["REFRESH_EXPIRED"] = -3] = "REFRESH_EXPIRED";
- IdentityStatus[IdentityStatus["OPTOUT"] = -4] = "OPTOUT";
-})(IdentityStatus = exports.IdentityStatus || (exports.IdentityStatus = {}));
-function notifyInitCallback(options, status, statusText, advertisingToken, logger) {
- if (options.callback) {
- const payload = {
- advertisingToken: advertisingToken,
- advertising_token: advertisingToken,
- status: status,
- statusText: statusText,
- };
- try {
- options.callback(payload);
- }
- catch (exception) {
- logger.warn('SDK init callback threw an exception', exception);
- }
- }
-}
-exports.notifyInitCallback = notifyInitCallback;
-
-
-/***/ }),
-
-/***/ 669:
-/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.LocalStorageManager = void 0;
-const Identity_1 = __webpack_require__(531);
-class LocalStorageManager {
- constructor(storageKey) {
- this._storageKey = storageKey;
- }
- setValue(identity) {
- const value = JSON.stringify(identity);
- localStorage.setItem(this._storageKey, value);
- }
- removeValue() {
- localStorage.removeItem(this._storageKey);
- }
- getValue() {
- return localStorage.getItem(this._storageKey);
- }
- loadIdentityFromLocalStorage() {
- const payload = this.getValue();
- if (payload) {
- const result = JSON.parse(payload);
- if ((0, Identity_1.isValidIdentity)(result))
- return result;
- if ((0, Identity_1.isOptoutIdentity)(result))
- return result;
- }
- return null;
- }
-}
-exports.LocalStorageManager = LocalStorageManager;
-
-
-/***/ }),
-
-/***/ 317:
-/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.PromiseHandler = void 0;
-const callbackManager_1 = __webpack_require__(230);
-class PromiseHandler {
- constructor(sdk) {
- this._promises = [];
- this._seenInitOrRejectAll = false;
- sdk.callbacks.push(this._handleEvent.bind(this));
- }
- _handleEvent(eventType, payload) {
- if (eventType !== callbackManager_1.EventType.InitCompleted && eventType !== callbackManager_1.EventType.IdentityUpdated)
- return;
- if (eventType === callbackManager_1.EventType.InitCompleted) {
- this._seenInitOrRejectAll = true;
- }
- if (!this._apiClient || !this._apiClient.hasActiveRequests()) {
- this._promises.forEach((p) => {
- if ('identity' in payload && payload.identity) {
- p.resolve(payload.identity.advertising_token);
- }
- else {
- p.reject(new Error(`No identity available.`));
- }
- });
- this._promises = [];
- }
- }
- rejectAllPromises(reason) {
- this._seenInitOrRejectAll = true;
- this._promises.forEach((p) => {
- p.reject(reason);
- });
- this._promises = [];
- }
- // n.b. If this has seen an SDK init and there is no active request or a reject-all call, it'll reply immediately with the provided token or rejection.
- // Otherwise, it will ignore the provided token and resolve with the identity available when the init event arrives
- createMaybeDeferredPromise(token) {
- if (!this._seenInitOrRejectAll || (this._apiClient && this._apiClient.hasActiveRequests())) {
- return new Promise((resolve, reject) => {
- this._promises.push({
- resolve,
- reject,
- });
- });
- }
- else {
- if (token)
- return Promise.resolve(token);
- else
- return Promise.reject(new Error('Identity not available'));
- }
- }
- registerApiClient(apiClient) {
- this._apiClient = apiClient;
- }
-}
-exports.PromiseHandler = PromiseHandler;
-
-
-/***/ }),
-
-/***/ 533:
-/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
-
-
-var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
-};
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.SdkBase = void 0;
-const package_json_1 = __webpack_require__(147);
-const Identity_1 = __webpack_require__(531);
-const initCallbacks_1 = __webpack_require__(755);
-const sdkOptions_1 = __webpack_require__(512);
-const logger_1 = __webpack_require__(980);
-const apiClient_1 = __webpack_require__(367);
-const callbackManager_1 = __webpack_require__(230);
-const clientSideIdentityOptions_1 = __webpack_require__(522);
-const diiNormalization_1 = __webpack_require__(838);
-const hashedDii_1 = __webpack_require__(254);
-const promiseHandler_1 = __webpack_require__(317);
-const storageManager_1 = __webpack_require__(505);
-const hash_1 = __webpack_require__(699);
-function hasExpired(expiry, now = Date.now()) {
- return expiry <= now;
-}
-class SdkBase {
- // Sets up nearly everything, but does not run SdkLoaded callbacks - derived classes must run them.
- constructor(existingCallbacks = undefined, product) {
- // Push functions to this array to receive event notifications
- this.callbacks = [];
- this._opts = {};
- this._initComplete = false;
- this._refreshTimerId = null;
- this._product = product;
- this._logger = (0, logger_1.MakeLogger)(console, product.name);
- const exception = new Error();
- this._logger.log(`Constructing an SDK!`, exception.stack);
- if (existingCallbacks)
- this.callbacks = existingCallbacks;
- this._tokenPromiseHandler = new promiseHandler_1.PromiseHandler(this);
- this._callbackManager = new callbackManager_1.CallbackManager(this, this._product.name, () => this.getIdentity(), this._logger);
- }
- static get VERSION() {
- return package_json_1.version;
- }
- static get DEFAULT_REFRESH_RETRY_PERIOD_MS() {
- return 5000;
- }
- init(opts) {
- this.initInternal(opts);
- }
- getAdvertisingToken() {
- var _a, _b;
- return (_b = (_a = this.getIdentity()) === null || _a === void 0 ? void 0 : _a.advertising_token) !== null && _b !== void 0 ? _b : undefined;
- }
- setIdentityFromEmail(email, opts) {
- return __awaiter(this, void 0, void 0, function* () {
- this._logger.log('Sending request', email);
- this.throwIfInitNotComplete('Cannot set identity before calling init.');
- (0, clientSideIdentityOptions_1.isClientSideIdentityOptionsOrThrow)(opts);
- const normalizedEmail = (0, diiNormalization_1.normalizeEmail)(email);
- if (normalizedEmail === undefined) {
- throw new Error('Invalid email address');
- }
- const emailHash = yield (0, hash_1.hashAndEncodeIdentifier)(email);
- yield this.callCstgAndSetIdentity({ emailHash: emailHash }, opts);
- });
- }
- setIdentityFromEmailHash(emailHash, opts) {
- return __awaiter(this, void 0, void 0, function* () {
- this.throwIfInitNotComplete('Cannot set identity before calling init.');
- (0, clientSideIdentityOptions_1.isClientSideIdentityOptionsOrThrow)(opts);
- if (!(0, hashedDii_1.isBase64Hash)(emailHash)) {
- throw new Error('Invalid hash');
- }
- yield this.callCstgAndSetIdentity({ emailHash: emailHash }, opts);
- });
- }
- setIdentity(identity) {
- if (this._apiClient)
- this._apiClient.abortActiveRequests();
- const validatedIdentity = this.validateAndSetIdentity(identity);
- if (validatedIdentity) {
- if ((0, Identity_1.isOptoutIdentity)(validatedIdentity)) {
- this._callbackManager.runCallbacks(callbackManager_1.EventType.OptoutReceived, {});
- }
- else {
- this.triggerRefreshOrSetTimer(validatedIdentity);
- }
- this._callbackManager.runCallbacks(callbackManager_1.EventType.IdentityUpdated, {});
- }
- }
- getIdentity() {
- return this._identity && !this.temporarilyUnavailable() && !(0, Identity_1.isOptoutIdentity)(this._identity)
- ? this._identity
- : null;
- }
- // When the SDK has been initialized, this function should return the token
- // from the most recent refresh request, if there is a request, wait for the
- // new token. Otherwise, returns a promise which will be resolved after init.
- getAdvertisingTokenAsync() {
- const token = this.getAdvertisingToken();
- return this._tokenPromiseHandler.createMaybeDeferredPromise(token !== null && token !== void 0 ? token : null);
- }
- /**
- * Deprecated
- */
- isLoginRequired() {
- return this.hasIdentity();
- }
- hasIdentity() {
- var _a;
- if (!this._initComplete)
- return undefined;
- return !(this.isLoggedIn() || ((_a = this._apiClient) === null || _a === void 0 ? void 0 : _a.hasActiveRequests()));
- }
- hasOptedOut() {
- if (!this._initComplete)
- return undefined;
- return (0, Identity_1.isOptoutIdentity)(this._identity);
- }
- disconnect() {
- this.abort(`${this._product.name} SDK disconnected.`);
- // Note: This silently fails to clear the cookie if init hasn't been called and a cookieDomain is used!
- if (this._storageManager)
- this._storageManager.removeValues();
- else
- new storageManager_1.StorageManager({}, this._product.cookieName, this._product.localStorageKey).removeValues();
- this._identity = undefined;
- this._callbackManager.runCallbacks(callbackManager_1.EventType.IdentityUpdated, {
- identity: null,
- });
- }
- // Note: This doesn't invoke callbacks. It's a hard, silent reset.
- abort(reason) {
- this._initComplete = true;
- this._tokenPromiseHandler.rejectAllPromises(reason !== null && reason !== void 0 ? reason : new Error(`${this._product.name} SDK aborted.`));
- if (this._refreshTimerId) {
- clearTimeout(this._refreshTimerId);
- this._refreshTimerId = null;
- }
- if (this._apiClient)
- this._apiClient.abortActiveRequests();
- }
- initInternal(opts) {
- var _a;
- if (this._initComplete) {
- throw new TypeError('Calling init() more than once is not allowed');
- }
- if (!(0, sdkOptions_1.isSDKOptionsOrThrow)(opts))
- throw new TypeError(`Options provided to ${this._product.name} init couldn't be validated.`);
- this._opts = opts;
- this._storageManager = new storageManager_1.StorageManager(Object.assign({}, opts), this._product.cookieName, this._product.localStorageKey);
- this._apiClient = new apiClient_1.ApiClient(opts, this._product.defaultBaseUrl, this._product.name);
- this._tokenPromiseHandler.registerApiClient(this._apiClient);
- let identity;
- if (this._opts.identity) {
- identity = this._opts.identity;
- }
- else {
- identity = this._storageManager.loadIdentityWithFallback();
- }
- const validatedIdentity = this.validateAndSetIdentity(identity);
- if (validatedIdentity && !(0, Identity_1.isOptoutIdentity)(validatedIdentity))
- this.triggerRefreshOrSetTimer(validatedIdentity);
- this._initComplete = true;
- (_a = this._callbackManager) === null || _a === void 0 ? void 0 : _a.runCallbacks(callbackManager_1.EventType.InitCompleted, {});
- if (this.hasOptedOut())
- this._callbackManager.runCallbacks(callbackManager_1.EventType.OptoutReceived, {});
- }
- isLoggedIn() {
- return this._identity && !hasExpired(this._identity.refresh_expires);
- }
- temporarilyUnavailable() {
- var _a;
- if (!this._identity && ((_a = this._apiClient) === null || _a === void 0 ? void 0 : _a.hasActiveRequests()))
- return true;
- if (this._identity &&
- hasExpired(this._identity.identity_expires) &&
- !hasExpired(this._identity.refresh_expires))
- return true;
- return false;
- }
- getIdentityStatus(identity) {
- if (!identity) {
- return {
- valid: false,
- errorMessage: 'Identity not available',
- status: initCallbacks_1.IdentityStatus.NO_IDENTITY,
- identity: null,
- };
- }
- if ((0, Identity_1.isOptoutIdentity)(identity)) {
- return {
- valid: false,
- errorMessage: 'User has opted out',
- status: initCallbacks_1.IdentityStatus.OPTOUT,
- identity: identity,
- };
- }
- if (!identity.advertising_token) {
- return {
- valid: false,
- errorMessage: 'advertising_token is not available or is not valid',
- status: initCallbacks_1.IdentityStatus.INVALID,
- identity: null,
- };
- }
- if (!identity.refresh_token) {
- return {
- valid: false,
- errorMessage: 'refresh_token is not available or is not valid',
- status: initCallbacks_1.IdentityStatus.INVALID,
- identity: null,
- };
- }
- if (hasExpired(identity.refresh_expires, Date.now())) {
- return {
- valid: false,
- errorMessage: 'Identity expired, refresh expired',
- status: initCallbacks_1.IdentityStatus.REFRESH_EXPIRED,
- identity: null,
- };
- }
- if (hasExpired(identity.identity_expires, Date.now())) {
- return {
- valid: true,
- errorMessage: 'Identity expired, refresh still valid',
- status: initCallbacks_1.IdentityStatus.EXPIRED,
- identity,
- };
- }
- if (typeof this._identity === 'undefined')
- return {
- valid: true,
- identity,
- status: initCallbacks_1.IdentityStatus.ESTABLISHED,
- errorMessage: 'Identity established',
- };
- return {
- valid: true,
- identity,
- status: initCallbacks_1.IdentityStatus.REFRESHED,
- errorMessage: 'Identity refreshed',
- };
- }
- validateAndSetIdentity(identity, status, statusText) {
- var _a, _b;
- if (!this._storageManager)
- throw new Error('Cannot set identity before calling init.');
- const validity = this.getIdentityStatus(identity);
- if (validity.valid &&
- validity.identity &&
- !(0, Identity_1.isOptoutIdentity)(this._identity) &&
- ((_a = validity.identity) === null || _a === void 0 ? void 0 : _a.advertising_token) === ((_b = this._identity) === null || _b === void 0 ? void 0 : _b.advertising_token))
- return validity.identity;
- this._identity = validity.identity;
- if (validity.valid && validity.identity) {
- this._storageManager.setIdentity(validity.identity);
- }
- else if (validity.status === initCallbacks_1.IdentityStatus.OPTOUT || status === initCallbacks_1.IdentityStatus.OPTOUT) {
- this._storageManager.setOptout();
- }
- else {
- this.abort();
- this._storageManager.removeValues();
- }
- (0, initCallbacks_1.notifyInitCallback)(this._opts, status !== null && status !== void 0 ? status : validity.status, statusText !== null && statusText !== void 0 ? statusText : validity.errorMessage, this.getAdvertisingToken(), this._logger);
- return validity.identity;
- }
- triggerRefreshOrSetTimer(validIdentity) {
- if (hasExpired(validIdentity.refresh_from, Date.now())) {
- this.refreshToken(validIdentity);
- }
- else {
- this.setRefreshTimer();
- }
- }
- setRefreshTimer() {
- var _a, _b;
- const timeout = (_b = (_a = this._opts) === null || _a === void 0 ? void 0 : _a.refreshRetryPeriod) !== null && _b !== void 0 ? _b : SdkBase.DEFAULT_REFRESH_RETRY_PERIOD_MS;
- if (this._refreshTimerId) {
- clearTimeout(this._refreshTimerId);
- }
- this._refreshTimerId = setTimeout(() => {
- var _a, _b;
- if (this.isLoginRequired())
- return;
- const validatedIdentity = this.validateAndSetIdentity((_b = (_a = this._storageManager) === null || _a === void 0 ? void 0 : _a.loadIdentity()) !== null && _b !== void 0 ? _b : null);
- if (validatedIdentity && !(0, Identity_1.isOptoutIdentity)(validatedIdentity))
- this.triggerRefreshOrSetTimer(validatedIdentity);
- this._refreshTimerId = null;
- }, timeout);
- }
- refreshToken(identity) {
- const apiClient = this._apiClient;
- if (!apiClient)
- throw new Error('Cannot refresh the token before calling init.');
- apiClient
- .callRefreshApi(identity)
- .then((response) => {
- switch (response.status) {
- case 'success':
- this.validateAndSetIdentity(response.identity, initCallbacks_1.IdentityStatus.REFRESHED, 'Identity refreshed');
- this.setRefreshTimer();
- break;
- case 'optout':
- this.validateAndSetIdentity(null, initCallbacks_1.IdentityStatus.OPTOUT, 'User opted out');
- this._callbackManager.runCallbacks(callbackManager_1.EventType.OptoutReceived, {});
- break;
- case 'expired_token':
- this.validateAndSetIdentity(null, initCallbacks_1.IdentityStatus.REFRESH_EXPIRED, 'Refresh token expired');
- break;
- }
- }, (reason) => {
- this._logger.warn(`Encountered an error refreshing the token`, reason);
- this.validateAndSetIdentity(identity);
- if (!hasExpired(identity.refresh_expires, Date.now()))
- this.setRefreshTimer();
- })
- .then(() => {
- this._callbackManager.runCallbacks(callbackManager_1.EventType.IdentityUpdated, {});
- }, (reason) => this._logger.warn(`Callbacks on identity event failed.`, reason));
- }
- callCstgAndSetIdentity(request, opts) {
- return __awaiter(this, void 0, void 0, function* () {
- const cstgResult = yield this._apiClient.callCstgApi(request, opts);
- if (cstgResult.status == 'success') {
- this.setIdentity(cstgResult.identity);
- }
- else if (cstgResult.status === 'optout') {
- this.validateAndSetIdentity(null, initCallbacks_1.IdentityStatus.OPTOUT);
- this._callbackManager.runCallbacks(callbackManager_1.EventType.OptoutReceived, {});
- this._callbackManager.runCallbacks(callbackManager_1.EventType.IdentityUpdated, {});
- }
- else {
- const errorText = 'Unexpected status received from CSTG endpoint.';
- this._logger.warn(errorText);
- throw new Error(errorText);
- }
- });
- }
- throwIfInitNotComplete(message) {
- if (!this._initComplete) {
- throw new Error(message);
- }
- }
-}
-exports.SdkBase = SdkBase;
-SdkBase.IdentityStatus = initCallbacks_1.IdentityStatus;
-SdkBase.EventType = callbackManager_1.EventType;
-
-
-/***/ }),
-
-/***/ 512:
-/***/ ((__unused_webpack_module, exports) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.isSDKOptionsOrThrow = void 0;
-function isSDKOptionsOrThrow(maybeOpts) {
- if (typeof maybeOpts !== 'object' || maybeOpts === null) {
- throw new TypeError('opts must be an object');
- }
- const opts = maybeOpts;
- if (opts.callback !== undefined && typeof opts.callback !== 'function') {
- throw new TypeError('opts.callback, if provided, must be a function');
- }
- if (typeof opts.refreshRetryPeriod !== 'undefined') {
- if (typeof opts.refreshRetryPeriod !== 'number')
- throw new TypeError('opts.refreshRetryPeriod must be a number');
- else if (opts.refreshRetryPeriod < 1000)
- throw new RangeError('opts.refreshRetryPeriod must be >= 1000');
- }
- return true;
-}
-exports.isSDKOptionsOrThrow = isSDKOptionsOrThrow;
-
-
-/***/ }),
-
-/***/ 980:
-/***/ ((__unused_webpack_module, exports) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.MakeLogger = void 0;
-function nop(...data) { }
-function MakeSafeLoggerFunction(fn) {
- if (typeof fn === 'function')
- return fn;
- return nop;
-}
-function MakeAnnotatedLoggerFunction(fn, annotation) {
- return (...data) => {
- if (typeof data[0] === 'string')
- fn(`[${annotation}] ${data[0]}`, ...data.slice(1));
- else
- fn(`[${annotation}]`, ...data);
- };
-}
-function MakeLoggerFunction(fn, annotation) {
- const safeFunction = MakeSafeLoggerFunction(fn);
- if (annotation)
- return MakeAnnotatedLoggerFunction(safeFunction, annotation);
- else
- return safeFunction;
-}
-function MakeLogger(logger, annotation) {
- return {
- debug: MakeLoggerFunction(logger.debug, annotation),
- error: MakeLoggerFunction(logger.error, annotation),
- info: MakeLoggerFunction(logger.info, annotation),
- log: MakeLoggerFunction(logger.log, annotation),
- trace: MakeLoggerFunction(logger.trace, annotation),
- warn: MakeLoggerFunction(logger.warn, annotation),
- };
-}
-exports.MakeLogger = MakeLogger;
-
-
-/***/ }),
-
-/***/ 505:
-/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-
-
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.StorageManager = void 0;
-const cookieManager_1 = __webpack_require__(852);
-const localStorageManager_1 = __webpack_require__(669);
-class StorageManager {
- constructor(opts, cookieName, localStorageKey) {
- this._opts = opts;
- this._cookieManager = new cookieManager_1.CookieManager(Object.assign({}, opts), cookieName);
- this._localStorageManager = new localStorageManager_1.LocalStorageManager(localStorageKey);
- }
- loadIdentityWithFallback() {
- const localStorageIdentity = this._localStorageManager.loadIdentityFromLocalStorage();
- const cookieIdentity = this._cookieManager.loadIdentityFromCookie();
- const shouldUseCookie = cookieIdentity &&
- (!localStorageIdentity ||
- cookieIdentity.identity_expires > localStorageIdentity.identity_expires);
- return shouldUseCookie ? cookieIdentity : localStorageIdentity;
- }
- loadIdentity() {
- return this._opts.useCookie
- ? this._cookieManager.loadIdentityFromCookie()
- : this._localStorageManager.loadIdentityFromLocalStorage();
- }
- setIdentity(identity) {
- this.setValue(identity);
- }
- setOptout() {
- const expiry = Date.now() + 72 * 60 * 60 * 1000; // 3 days - need to pick something
- const optout = {
- refresh_expires: expiry,
- identity_expires: expiry,
- status: 'optout',
- };
- this.setValue(optout);
- }
- setValue(value) {
- if (this._opts.useCookie) {
- this._cookieManager.setCookie(value);
- return;
- }
- this._localStorageManager.setValue(value);
- if (this._opts.useCookie === false &&
- this._localStorageManager.loadIdentityFromLocalStorage()) {
- this._cookieManager.removeCookie();
- }
- }
- removeValues() {
- this._cookieManager.removeCookie();
- this._localStorageManager.removeValue();
- }
-}
-exports.StorageManager = StorageManager;
-
-
-/***/ }),
-
-/***/ 890:
-/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
-
-
-var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- var desc = Object.getOwnPropertyDescriptor(m, k);
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
- desc = { enumerable: true, get: function() { return m[k]; } };
- }
- Object.defineProperty(o, k2, desc);
-}) : (function(o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- o[k2] = m[k];
-}));
-var __exportStar = (this && this.__exportStar) || function(m, exports) {
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
-};
-var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
-};
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.sdkWindow = exports.__uid2InternalHandleScriptLoad = exports.UID2 = exports.UID2Helper = void 0;
-const callbackManager_1 = __webpack_require__(230);
-const clientSideIdentityOptions_1 = __webpack_require__(522);
-const diiNormalization_1 = __webpack_require__(838);
-const hashedDii_1 = __webpack_require__(254);
-const hash_1 = __webpack_require__(699);
-const sdkBase_1 = __webpack_require__(533);
-__exportStar(__webpack_require__(479), exports);
-class UID2Helper {
- normalizeEmail(email) {
- return (0, diiNormalization_1.normalizeEmail)(email);
- }
- hashIdentifier(normalizedEmail) {
- return (0, hash_1.hashIdentifier)(normalizedEmail);
- }
- hashAndEncodeIdentifier(normalizedEmail) {
- return __awaiter(this, void 0, void 0, function* () {
- return yield (0, hash_1.hashAndEncodeIdentifier)(normalizedEmail);
- });
- }
- isNormalizedPhone(phone) {
- return (0, diiNormalization_1.isNormalizedPhone)(phone);
- }
-}
-exports.UID2Helper = UID2Helper;
-class UID2 extends sdkBase_1.SdkBase {
- constructor(existingCallbacks = undefined, callbackContainer = {}) {
- super(existingCallbacks, UID2.Uid2Details);
- const runCallbacks = () => {
- this._callbackManager.runCallbacks(callbackManager_1.EventType.SdkLoaded, {});
- };
- if (window.__uid2 instanceof UID2) {
- runCallbacks();
- }
- else {
- // Need to defer running callbacks until this is assigned to the window global
- callbackContainer.callback = runCallbacks;
- }
- }
- // Deprecated. Integrators should never access the cookie directly!
- static get COOKIE_NAME() {
- console.warn('Detected access to UID2.COOKIE_NAME. This is deprecated and will be removed in the future. Integrators should not access the cookie directly.');
- return UID2.cookieName;
- }
- static get Uid2Details() {
- return {
- name: 'UID2',
- defaultBaseUrl: 'https://prod.uidapi.com',
- localStorageKey: 'UID2-sdk-identity',
- cookieName: UID2.cookieName,
- };
- }
- static setupGoogleTag() {
- UID2.setupGoogleSecureSignals();
- }
- static setupGoogleSecureSignals() {
- if (window.__uid2SecureSignalProvider)
- window.__uid2SecureSignalProvider.registerSecureSignalProvider();
- }
- setIdentityFromPhone(phone, opts) {
- return __awaiter(this, void 0, void 0, function* () {
- this.throwIfInitNotComplete('Cannot set identity before calling init.');
- (0, clientSideIdentityOptions_1.isClientSideIdentityOptionsOrThrow)(opts);
- if (!(0, diiNormalization_1.isNormalizedPhone)(phone)) {
- throw new Error('Invalid phone number');
- }
- const phoneHash = yield (0, hash_1.hashAndEncodeIdentifier)(phone);
- yield this.callCstgAndSetIdentity({ phoneHash: phoneHash }, opts);
- });
- }
- setIdentityFromPhoneHash(phoneHash, opts) {
- return __awaiter(this, void 0, void 0, function* () {
- this.throwIfInitNotComplete('Cannot set identity before calling init.');
- (0, clientSideIdentityOptions_1.isClientSideIdentityOptionsOrThrow)(opts);
- if (!(0, hashedDii_1.isBase64Hash)(phoneHash)) {
- throw new Error('Invalid hash');
- }
- yield this.callCstgAndSetIdentity({ phoneHash: phoneHash }, opts);
- });
- }
-}
-exports.UID2 = UID2;
-UID2.cookieName = '__uid_2';
-function __uid2InternalHandleScriptLoad() {
- var _a;
- const callbacks_uid2 = ((_a = window === null || window === void 0 ? void 0 : window.__uid2) === null || _a === void 0 ? void 0 : _a.callbacks) || [];
- const callbackContainer = {};
- window.__uid2 = new UID2(callbacks_uid2, callbackContainer);
- window.__uid2Helper = new UID2Helper();
- if (callbackContainer.callback)
- callbackContainer.callback();
-}
-exports.__uid2InternalHandleScriptLoad = __uid2InternalHandleScriptLoad;
-__uid2InternalHandleScriptLoad();
-exports.sdkWindow = globalThis.window;
-
-
-/***/ }),
-
-/***/ 147:
-/***/ ((module) => {
-
-module.exports = JSON.parse('{"name":"@uid2/uid2-sdk","version":"3.3.0","description":"UID2 Client SDK","main":"lib/src/uid2Sdk.js","types":"lib/src/uid2Sdk.d.ts","files":["/lib"],"author":"The Trade Desk","license":"Apache 2.0","wallaby":{"delays":{"run":1000}},"scripts":{"lint":"eslint -c .eslintrc.js . ../static/js/uid2-sdk-2.0.0.js ../static/js/uid2-sdk-1.0.0.js","test":"jest","build":"webpack","build-with-sourcemaps":"webpack --mode=production --env prodSourceMaps=true","build-package":"tsc","watch":"webpack watch --mode=development","webpack-dev-server":"webpack-dev-server --config webpack-dev-server.config.js --hot --port 9091","uid2-examples":"webpack --mode=development --env outputToExamples=true","build:esp":"webpack --env espOnly=true"},"engines":{"node":">=18"},"jest":{"preset":"ts-jest","testEnvironment":"jsdom","setupFilesAfterEnv":["./setupJest.js"],"testPathIgnorePatterns":["/node_modules/","/dist/"]},"devDependencies":{"@jest/globals":"^29.2.2","@types/jest":"^29.2.0","@types/node":"^18.11.3","@typescript-eslint/eslint-plugin":"^5.40.1","@typescript-eslint/parser":"^5.40.1","eslint":"^8.25.0","eslint-config-airbnb-typescript":"^17.0.0","eslint-plugin-import":"^2.26.0","eslint-plugin-promise":"^6.1.1","eslint-plugin-simple-import-sort":"^8.0.0","eslint-plugin-testing-library":"^5.9.0","jest":"^29.2.1","jest-environment-jsdom":"^29.2.1","jsdom":"^20.0.1","ts-jest":"^29.0.3","ts-loader":"^9.4.1","typescript":"^4.8.4","webpack":"^5.74.0","webpack-cli":"^4.10.0","webpack-dev-server":"^4.15.1"}}');
-
-/***/ })
-
-/******/ });
-/************************************************************************/
-/******/ // The module cache
-/******/ var __webpack_module_cache__ = {};
-/******/
-/******/ // The require function
-/******/ function __webpack_require__(moduleId) {
-/******/ // Check if module is in cache
-/******/ var cachedModule = __webpack_module_cache__[moduleId];
-/******/ if (cachedModule !== undefined) {
-/******/ return cachedModule.exports;
-/******/ }
-/******/ // Create a new module (and put it into the cache)
-/******/ var module = __webpack_module_cache__[moduleId] = {
-/******/ // no module.id needed
-/******/ // no module.loaded needed
-/******/ exports: {}
-/******/ };
-/******/
-/******/ // Execute the module function
-/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
-/******/
-/******/ // Return the exports of the module
-/******/ return module.exports;
-/******/ }
-/******/
-/************************************************************************/
-/******/
-/******/ // startup
-/******/ // Load entry module and return exports
-/******/ // This entry module is referenced by other modules so it can't be inlined
-/******/ var __webpack_exports__ = __webpack_require__(890);
-/******/
-/******/ })()
-;
\ No newline at end of file
diff --git a/tools/reverse-proxy/README.md b/tools/reverse-proxy/README.md
index 77e80535..6a5df13e 100644
--- a/tools/reverse-proxy/README.md
+++ b/tools/reverse-proxy/README.md
@@ -80,6 +80,7 @@ Add these entries:
127.0.0.1 prebid-client-server.sample-dev.com
127.0.0.1 prebid-deferred.sample-dev.com
127.0.0.1 prebid-secure-signals.sample-dev.com
+127.0.0.1 hashing-tool.sample-dev.com
```
Flush DNS cache after saving:
@@ -119,6 +120,7 @@ Go to **https://sample-dev.com** — this index page has clickable links to all
| `https://prebid-client-server.sample-dev.com` | Prebid Client Server | 3052 |
| `https://prebid-deferred.sample-dev.com` | Prebid Client Side Deferred | 3053 |
| `https://prebid-secure-signals.sample-dev.com` | Prebid Secure Signals | 3061 |
+| `https://hashing-tool.sample-dev.com` | Hashing Tool | 3071 |
---
@@ -155,6 +157,7 @@ You can skip all certificate setup and access services directly via localhost:
| `http://localhost:3051` | Prebid Client Side |
| `http://localhost:3052` | Prebid Client Server |
| `http://localhost:3053` | Prebid Client Side Deferred |
+| `http://localhost:3071` | Hashing Tool |
| `http://localhost:3031` | JavaScript SDK Client Side |
| *(etc.)* |
diff --git a/tools/reverse-proxy/default.conf.template b/tools/reverse-proxy/default.conf.template
index 8d1831a1..b5e35d1c 100644
--- a/tools/reverse-proxy/default.conf.template
+++ b/tools/reverse-proxy/default.conf.template
@@ -20,7 +20,7 @@ server {
location / {
add_header Content-Type text/html;
# Use protocol-relative URLs (//) so they work with both http and https
- return 200 "UID2 Sample Pages UID2 Sample Pages Access services using the following subdomains:
js-client-side.${DOMAIN} js-client-server.${DOMAIN} js-react.${DOMAIN} server-side.${DOMAIN} secure-signals-client-server.${DOMAIN} secure-signals-client-side.${DOMAIN} secure-signals-server-side.${DOMAIN} secure-signals-react.${DOMAIN} prebid-client.${DOMAIN} prebid-client-server.${DOMAIN} prebid-deferred.${DOMAIN} prebid-secure-signals.${DOMAIN} Note: For local development, add ${DOMAIN} and all subdomains to your hosts file (127.0.0.1) to use them. Example: 127.0.0.1 ${DOMAIN} js-client-side.${DOMAIN} ...
";
+ return 200 "UID2 Sample Pages UID2 Sample Pages Access services using the following subdomains:
js-client-side.${DOMAIN} js-client-server.${DOMAIN} js-react.${DOMAIN} server-side.${DOMAIN} secure-signals-client-server.${DOMAIN} secure-signals-client-side.${DOMAIN} secure-signals-server-side.${DOMAIN} secure-signals-react.${DOMAIN} prebid-client.${DOMAIN} prebid-client-server.${DOMAIN} prebid-deferred.${DOMAIN} prebid-secure-signals.${DOMAIN} hashing-tool.${DOMAIN} Note: For local development, add ${DOMAIN} and all subdomains to your hosts file (127.0.0.1) to use them. Example: 127.0.0.1 ${DOMAIN} js-client-side.${DOMAIN} ...
";
}
}
@@ -345,3 +345,27 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
}
}
+
+# Hashing Tool (port 3071)
+server {
+ listen 80;
+ listen 443 ssl;
+ server_name hashing-tool.${DOMAIN} *.hashing-tool.${DOMAIN};
+
+ ssl_certificate /etc/nginx/certs/cert.crt;
+ ssl_certificate_key /etc/nginx/certs/cert.key;
+ ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_ciphers HIGH:!aNULL:!MD5;
+
+ location / {
+ proxy_pass http://${HASHING_TOOL_BACKEND}:3071;
+ proxy_redirect off;
+ proxy_connect_timeout 5s;
+ proxy_send_timeout 60s;
+ proxy_read_timeout 60s;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+}
diff --git a/tools/reverse-proxy/docker-entrypoint.sh b/tools/reverse-proxy/docker-entrypoint.sh
index bb31cb0c..ecc1522a 100644
--- a/tools/reverse-proxy/docker-entrypoint.sh
+++ b/tools/reverse-proxy/docker-entrypoint.sh
@@ -38,6 +38,7 @@ if [ "${BACKEND_HOST+set}" = "set" ] && [ -z "$BACKEND_HOST" ]; then
PREBID_CLIENT_SERVER_BACKEND=${PREBID_CLIENT_SERVER_BACKEND:-prebid-client-server}
PREBID_CLIENT_SIDE_DEFERRED_BACKEND=${PREBID_CLIENT_SIDE_DEFERRED_BACKEND:-prebid-client-side-deferred}
PREBID_SECURE_SIGNALS_CLIENT_SIDE_BACKEND=${PREBID_SECURE_SIGNALS_CLIENT_SIDE_BACKEND:-prebid-secure-signals-client-side}
+ HASHING_TOOL_BACKEND=${HASHING_TOOL_BACKEND:-hashing-tool}
else
# BACKEND_HOST is unset (defaults to localhost) or set to a value - use it for all services
BACKEND_HOST_VALUE=${BACKEND_HOST:-localhost}
@@ -53,6 +54,7 @@ else
PREBID_CLIENT_SERVER_BACKEND=${PREBID_CLIENT_SERVER_BACKEND:-$BACKEND_HOST_VALUE}
PREBID_CLIENT_SIDE_DEFERRED_BACKEND=${PREBID_CLIENT_SIDE_DEFERRED_BACKEND:-$BACKEND_HOST_VALUE}
PREBID_SECURE_SIGNALS_CLIENT_SIDE_BACKEND=${PREBID_SECURE_SIGNALS_CLIENT_SIDE_BACKEND:-$BACKEND_HOST_VALUE}
+ HASHING_TOOL_BACKEND=${HASHING_TOOL_BACKEND:-$BACKEND_HOST_VALUE}
fi
# Export all variables for envsubst
@@ -69,9 +71,10 @@ export PREBID_CLIENT_BACKEND
export PREBID_CLIENT_SERVER_BACKEND
export PREBID_CLIENT_SIDE_DEFERRED_BACKEND
export PREBID_SECURE_SIGNALS_CLIENT_SIDE_BACKEND
+export HASHING_TOOL_BACKEND
# Substitute environment variables in the template
-envsubst '${DOMAIN} ${JS_CLIENT_SIDE_BACKEND} ${JS_CLIENT_SERVER_BACKEND} ${JS_REACT_CLIENT_SIDE_BACKEND} ${SERVER_SIDE_BACKEND} ${SECURE_SIGNALS_CLIENT_SERVER_BACKEND} ${SECURE_SIGNALS_CLIENT_SIDE_BACKEND} ${SECURE_SIGNALS_SERVER_SIDE_BACKEND} ${SECURE_SIGNALS_REACT_CLIENT_SIDE_BACKEND} ${PREBID_CLIENT_BACKEND} ${PREBID_CLIENT_SERVER_BACKEND} ${PREBID_CLIENT_SIDE_DEFERRED_BACKEND} ${PREBID_SECURE_SIGNALS_CLIENT_SIDE_BACKEND}' < /etc/nginx/templates/default.conf.template > /etc/nginx/conf.d/default.conf
+envsubst '${DOMAIN} ${JS_CLIENT_SIDE_BACKEND} ${JS_CLIENT_SERVER_BACKEND} ${JS_REACT_CLIENT_SIDE_BACKEND} ${SERVER_SIDE_BACKEND} ${SECURE_SIGNALS_CLIENT_SERVER_BACKEND} ${SECURE_SIGNALS_CLIENT_SIDE_BACKEND} ${SECURE_SIGNALS_SERVER_SIDE_BACKEND} ${SECURE_SIGNALS_REACT_CLIENT_SIDE_BACKEND} ${PREBID_CLIENT_BACKEND} ${PREBID_CLIENT_SERVER_BACKEND} ${PREBID_CLIENT_SIDE_DEFERRED_BACKEND} ${PREBID_SECURE_SIGNALS_CLIENT_SIDE_BACKEND} ${HASHING_TOOL_BACKEND}' < /etc/nginx/templates/default.conf.template > /etc/nginx/conf.d/default.conf
# Test nginx configuration
nginx -t
diff --git a/web-integrations/javascript-sdk/client-server/public/stylesheets/app.css b/web-integrations/javascript-sdk/client-server/public/stylesheets/app.css
index 45f944fc..4daeb56e 100644
--- a/web-integrations/javascript-sdk/client-server/public/stylesheets/app.css
+++ b/web-integrations/javascript-sdk/client-server/public/stylesheets/app.css
@@ -200,7 +200,12 @@ a:hover {
margin-top: 2rem;
}
-.form-inline {
+.form.top-form {
+ margin-top: 0;
+ margin-bottom: 2rem;
+}
+
+.email_prompt {
display: flex;
gap: 0;
max-width: 600px;
@@ -209,7 +214,7 @@ a:hover {
overflow: hidden;
}
-.email-input {
+#email {
flex: 1;
padding: 0.875rem 1.25rem;
border: 2px solid var(--border-color);
@@ -220,11 +225,11 @@ a:hover {
transition: border-color 0.2s ease;
}
-.email-input:focus {
+#email:focus {
border-color: var(--primary-orange);
}
-.email-input::placeholder {
+#email::placeholder {
color: var(--text-gray);
}
@@ -260,6 +265,11 @@ a:hover {
border-radius: 8px;
}
+/* Rounded corners for login button in form */
+#login_form .button {
+ border-radius: 0 8px 8px 0;
+}
+
/* Tooltip Styles - Matching Self-Serve Portal */
.tooltip-wrapper {
display: inline-flex;
@@ -353,11 +363,11 @@ a:hover {
padding: 1.5rem;
}
- .form-inline {
+ .email_prompt {
flex-direction: column;
}
- .email-input {
+ #email {
border-right: 2px solid var(--border-color);
border-bottom: none;
}
diff --git a/web-integrations/javascript-sdk/client-server/views/index.html b/web-integrations/javascript-sdk/client-server/views/index.html
index 06ed4269..e6c30d88 100644
--- a/web-integrations/javascript-sdk/client-server/views/index.html
+++ b/web-integrations/javascript-sdk/client-server/views/index.html
@@ -74,6 +74,27 @@
<%- include('intro.html'); -%>
+
+
+
+ Clear <%- identityName %>
+
+
+ Try Another Email
+
+
<%- identityName %> Integration Status
@@ -148,33 +169,6 @@ <%- identityName %> Integration Status
-
-
-
-
-
-
-
-
-
-
diff --git a/web-integrations/javascript-sdk/react-client-side/src/ClientSideApp.tsx b/web-integrations/javascript-sdk/react-client-side/src/ClientSideApp.tsx
index 71681c9b..3461fcaa 100644
--- a/web-integrations/javascript-sdk/react-client-side/src/ClientSideApp.tsx
+++ b/web-integrations/javascript-sdk/react-client-side/src/ClientSideApp.tsx
@@ -110,9 +110,34 @@ const ClientSideApp = () => {
This example demonstrates how a content publisher can integrate {IDENTITY_NAME} using client-side token generation with React, where the SDK generates tokens directly in the browser using public credentials. For documentation, see the{' '}
Client-Side Integration Guide for JavaScript
- .
+ . [Source Code ]
+ {/* Generate/Clear buttons at the top for easy access */}
+ {showLoginForm ? (
+
+ ) : (
+
+
+ Clear {IDENTITY_NAME}
+
+
+ )}
+
{IDENTITY_NAME} Integration Status
@@ -198,30 +223,6 @@ const ClientSideApp = () => {
-
- {showLoginForm ? (
-
- ) : (
-
-
- Clear {IDENTITY_NAME}
-
-
- )}
diff --git a/web-integrations/javascript-sdk/react-client-side/src/styles/app.css b/web-integrations/javascript-sdk/react-client-side/src/styles/app.css
index 3ef2298b..d24c7d94 100644
--- a/web-integrations/javascript-sdk/react-client-side/src/styles/app.css
+++ b/web-integrations/javascript-sdk/react-client-side/src/styles/app.css
@@ -213,6 +213,11 @@ a:hover {
margin-top: 2rem;
}
+.form.top-form {
+ margin-top: 0;
+ margin-bottom: 2rem;
+}
+
.email_prompt {
display: flex;
gap: 0;
@@ -272,6 +277,11 @@ a:hover {
border-radius: 8px;
}
+/* Rounded corners for login button in form */
+#login_form .button {
+ border-radius: 0 8px 8px 0;
+}
+
/* Success Message */
.message {
background: linear-gradient(135deg, rgba(34, 197, 94, 0.1) 0%, rgba(34, 197, 94, 0.05) 100%);
diff --git a/web-integrations/server-side/public/stylesheets/app.css b/web-integrations/server-side/public/stylesheets/app.css
index 77cf84ff..a5b9d21c 100644
--- a/web-integrations/server-side/public/stylesheets/app.css
+++ b/web-integrations/server-side/public/stylesheets/app.css
@@ -207,7 +207,12 @@ a:hover {
margin-top: 2rem;
}
-.form-inline {
+.form.top-form {
+ margin-top: 0;
+ margin-bottom: 2rem;
+}
+
+.email_prompt {
display: flex;
gap: 0;
max-width: 600px;
@@ -216,7 +221,7 @@ a:hover {
overflow: hidden;
}
-.email-input {
+#email {
flex: 1;
padding: 0.875rem 1.25rem;
border: 2px solid var(--border-color);
@@ -227,11 +232,11 @@ a:hover {
transition: border-color 0.2s ease;
}
-.email-input:focus {
+#email:focus {
border-color: var(--primary-orange);
}
-.email-input::placeholder {
+#email::placeholder {
color: var(--text-gray);
}
@@ -267,6 +272,11 @@ a:hover {
border-radius: 8px;
}
+/* Rounded corners for login button in form */
+#login_form .button {
+ border-radius: 0 8px 8px 0;
+}
+
/* Tooltip Styles - Matching Self-Serve Portal */
.tooltip-wrapper {
display: inline-flex;
@@ -345,7 +355,7 @@ a:hover {
@media (max-width: 1024px) {
.page-wrapper {
flex-direction: column;
-}
+ }
.sidebar {
position: static;
@@ -360,11 +370,11 @@ a:hover {
padding: 1.5rem;
}
- .form-inline {
+ .email_prompt {
flex-direction: column;
}
- .email-input {
+ #email {
border-right: 2px solid var(--border-color);
border-bottom: none;
}
diff --git a/web-integrations/server-side/views/index.html b/web-integrations/server-side/views/index.html
index 73706163..6e49ac9d 100644
--- a/web-integrations/server-side/views/index.html
+++ b/web-integrations/server-side/views/index.html
@@ -28,6 +28,31 @@ Server-Side <%- identityName %> Integration Example
[Source Code ]
+
+ <% if (!identity && !isOptout) { %>
+
+ <% } else if (isOptout) { %>
+
+ Try Another Email
+
+ <% } else { %>
+
+ Clear <%- identityName %>
+
+ <% } %>
+
<%- identityName %> Integration Status
@@ -102,31 +127,6 @@ <%- identityName %> Integration Status
<%= identity ? JSON.stringify(identity, null, 2) : 'null' %>
-
- <% if (!identity && !isOptout) { %>
-
- <% } else if (isOptout) { %>
-
- Try Another Email
-
- <% } else { %>
-
- Clear <%- identityName %>
-
- <% } %>