Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions lib/edge/uid2_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ type Uid2TokenResponse = {
RefreshResponseKey: string;
};

type Uid2RefreshBody = {
advertising_token: string;
refresh_token: string;
identity_expires: number;
refresh_from: number;
refresh_expires: number;
refresh_response_key: string;
};

function Uid2Token(config: ResolvedConfig, id: string): Promise<Uid2TokenResponse> {
return fetch("/uid2/token", config, {
method: "POST",
Expand All @@ -20,6 +29,29 @@ function Uid2Token(config: ResolvedConfig, id: string): Promise<Uid2TokenRespons
});
}

export { Uid2Token };
async function decryptUid2Response(encryptedBase64: string, responseKeyBase64: string): Promise<string> {
const encryptedBytes = Uint8Array.from(atob(encryptedBase64), (c) => c.charCodeAt(0));
const keyBytes = Uint8Array.from(atob(responseKeyBase64), (c) => c.charCodeAt(0));
const nonce = encryptedBytes.slice(0, 12);
const ciphertext = encryptedBytes.slice(12);
const cryptoKey = await crypto.subtle.importKey("raw", keyBytes, { name: "AES-GCM" }, false, ["decrypt"]);
const decryptedBuffer = await crypto.subtle.decrypt({ name: "AES-GCM", iv: nonce }, cryptoKey, ciphertext);
return new TextDecoder().decode(decryptedBuffer);
}

async function Uid2Refresh(refreshToken: string, refreshResponseKey: string): Promise<Uid2RefreshBody | null> {
const response = await globalThis.fetch("https://prod.uidapi.com/v2/token/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: refreshToken,
});
if (!response.ok) return null;
const encrypted = await response.text();
const decrypted = await decryptUid2Response(encrypted, refreshResponseKey);
const { body } = JSON.parse(decrypted) as { body?: Uid2RefreshBody };
return body ?? null;
}

export { Uid2Token, Uid2Refresh };
export default Uid2Token;
export type { Uid2TokenResponse };
export type { Uid2TokenResponse, Uid2RefreshBody };
6 changes: 5 additions & 1 deletion lib/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { ProfileTraits } from "./edge/profile";
import type { PageContextConfig, ContextData } from "./core/context";
import { extractContext, normalizeContextConfig } from "./core/context";
import { Identify } from "./edge/identify";
import { Uid2Token, Uid2TokenResponse } from "./edge/uid2_token";
import { Uid2Token, Uid2TokenResponse, Uid2Refresh, Uid2RefreshBody } from "./edge/uid2_token";
import { Resolve, ResolveResponse } from "./edge/resolve";
import { Site, SiteResponse, SiteFromCache } from "./edge/site";
import { isObject } from "./core/utils";
Expand Down Expand Up @@ -73,6 +73,10 @@ class OptableSDK {
return Uid2Token(this.dcn, id);
}

async uid2Refresh(refreshToken: string, refreshResponseKey: string): Promise<Uid2RefreshBody | null> {
return Uid2Refresh(refreshToken, refreshResponseKey);
}

async targeting(input: string | TargetingRequest = "__passport__"): Promise<TargetingResponse> {
const request = normalizeTargetingRequest(input);

Expand Down
Loading