API Reference
Complete API reference for Relay client and server
API Reference
Complete reference for all Relay types, interfaces, and methods.
Package Exports
Relay provides two entry points to avoid loading React Native dependencies on the server:
@korsolutions/relay- Server-side exports (safe for Node.js, Hono, Express, etc.)@korsolutions/relay/client- Client-side exports (requires React Native/Expo)
// Server-side (Node.js, Hono, etc.)
import { createRelayServer } from "@korsolutions/relay";
// Client-side (React Native/Expo)
import { RelayExpoClient } from "@korsolutions/relay/client";Client API
RelayExpoClient
The main client class for interacting with Relay from your Expo/React Native app.
Constructor
new RelayExpoClient(options: RelayClientOptions)Parameters:
options.serverUrl(string): Base URL of your Relay API server
Example:
const client = new RelayExpoClient({
serverUrl: "https://api.yourapp.com"
});capture()
Captures a device fingerprint and associates it with a URL.
capture(url: string): Promise<void>Parameters:
url(string): The URL to associate with this device fingerprint
Returns: Promise that resolves when capture is complete
Throws: RelayError if the request fails
Example:
await client.capture("https://yourapp.com/invite/abc123");process()
Processes the current device fingerprint and retrieves any matching deferred link.
process(): Promise<ProcessResponse>Returns: Promise that resolves to a ProcessResponse object
Throws: RelayError if the request fails
Example:
const result = await client.process();
if (result.url) {
console.log("Match found:", result.url);
}Types
RelayClientOptions
interface RelayClientOptions {
serverUrl: string;
}ProcessResponse
interface ProcessResponse {
url: string | null;
}Properties:
url: The matched deferred link URL, ornullif no match was found
RelayError
class RelayError extends Error {
name: "RelayError";
message: string;
}Error thrown when Relay operations fail.
Server API
createRelayServer()
Creates a Relay server instance.
createRelayServer(config: RelayConfig): RelayServerParameters:
config: Server configuration object
Returns: RelayServer instance
Example:
const server = createRelayServer({
fingerprint: {
methods: {
storeFingerprint: async (fp, hash) => { /* ... */ },
getFingerprintByHash: async (hash) => { /* ... */ },
},
},
deferredLink: {
methods: {
storeDeferredLink: async (link) => { /* ... */ },
getDeferredLinkByFingerprintHash: async (hash) => { /* ... */ },
deleteDeferredLink: async (id) => { /* ... */ },
},
},
});Types
RelayServer
interface RelayServer {
handler: (request: Request, authCtx?: RelayAuthContext) => Promise<Response>;
}Methods:
handler: Request handler function for processing Relay API requestsrequest: The incoming HTTP requestauthCtx(optional): Authentication context containing user information
RelayConfig
interface RelayConfig {
fingerprint: FingerprintSdkOptions;
deferredLink: DeferredLinkSdkOptions;
hooks?: {
onMatchFound?: (deferredLink: DeferredLink, authCtx?: RelayAuthContext) => Promise<void> | void;
};
}Properties:
fingerprint: Fingerprint storage and processing configurationdeferredLink: Deferred link storage configurationhooks: Optional event hooksonMatchFound: Called when a fingerprint match is found. Receives the matched deferred link and optional auth context
RelayAuthContext
interface RelayAuthContext {
userId: string | null;
}Properties:
userId: The authenticated user's ID, ornullif not authenticated
Usage:
Pass auth context to the handler to make user information available in hooks:
export const POST = async (request: Request) => {
const user = await getUserFromSession(request);
return relayServer.handler(request, {
userId: user?.id ?? null,
});
};FingerprintSdkOptions
interface FingerprintSdkOptions {
methods: {
hashFingerprint?: (data: Fingerprint) => Promise<string>;
parseFingerprint?: (data: any) => Fingerprint;
storeFingerprint: (data: Fingerprint, hash: string) => Promise<FingerprintDbRecord>;
getFingerprintByHash: (hash: string) => Promise<FingerprintDbRecord | null>;
};
}Methods:
hashFingerprint(optional): Custom fingerprint hashing function. Defaults to SHA-256.parseFingerprint(optional): Custom fingerprint parsing function. Defaults to Zod validation.storeFingerprint: Store a fingerprint with its hashgetFingerprintByHash: Retrieve a fingerprint by its hash
DeferredLinkSdkOptions
interface DeferredLinkSdkOptions {
expiryDays?: number; // Default: 7
autoCleanup?: boolean; // Default: true
methods: DeferredLinkMethods;
}Properties:
expiryDays: Number of days before links expire (not enforced by SDK, implement in your storage)autoCleanup: Whether to automatically clean up expired links (not enforced by SDK, implement in your storage)methods: Deferred link storage methods
DeferredLinkMethods
interface DeferredLinkMethods {
storeDeferredLink: (deferredLink: DeferredLink) => Promise<void>;
getDeferredLinkByFingerprintHash: (fingerprintHash: string) => Promise<DeferredLink | null>;
deleteDeferredLink: (id: string) => Promise<void>;
}Methods:
storeDeferredLink: Store a new deferred linkgetDeferredLinkByFingerprintHash: Get a deferred link by fingerprint hashdeleteDeferredLink: Delete a deferred link by ID
Data Types
Fingerprint
interface Fingerprint {
clipboardValue: string | null;
ipAddress: string | null;
deviceManufacturer: string | null;
deviceModel: string | null;
osName: string | null;
osVersion: string | null;
screenWidth: number;
screenHeight: number;
pixelRatio: number;
timeZone: string | null;
languageTags: string[];
}Properties:
clipboardValue: Current clipboard contentipAddress: User's IP address (captured server-side from request headers)deviceManufacturer: Device manufacturer (e.g., "Apple", "Samsung")deviceModel: Device model (e.g., "iPhone 15 Pro")osName: Operating system name (e.g., "iOS", "Android")osVersion: Operating system version (e.g., "17.1")screenWidth: Screen width in pixelsscreenHeight: Screen height in pixelspixelRatio: Device pixel ratiotimeZone: User's timezone (e.g., "America/New_York")languageTags: Array of language codes (e.g., ["en-US", "es-MX"])
FingerprintDbRecord
interface FingerprintDbRecord extends Fingerprint {
hash: string;
createdDate: Date;
updatedDate?: Date;
}Properties:
- All properties from
Fingerprint hash: SHA-256 hash of the fingerprintcreatedDate: When the fingerprint was createdupdatedDate: When the fingerprint was last updated (optional)
DeferredLink
interface DeferredLink {
id: string;
fingerprintHash: string;
url: string;
createdDate: Date;
}Properties:
id: Unique identifier for the deferred link (UUID)fingerprintHash: Hash of the associated fingerprinturl: The URL to return when a match is foundcreatedDate: When the link was created
CaptureRequest
interface CaptureRequest {
deferredLinkUrl: string;
fingerprint: Fingerprint;
}Properties:
deferredLinkUrl: The URL to associate with the fingerprintfingerprint: The device fingerprint data
ProcessRequest
type ProcessRequest = Fingerprint;The process request is simply a Fingerprint object.
API Endpoints
POST /relay/capture
Capture a device fingerprint.
Request:
{
deferredLinkUrl: string;
fingerprint: Fingerprint;
}Response:
{
success: true
}Status Codes:
200: Success400: Invalid request body500: Server error
POST /relay/process
Process a fingerprint and get matching link.
Request:
FingerprintResponse:
{
url: string | null
}Status Codes:
200: Success (even if no match found, checkurlfornull)400: Invalid request body500: Server error
Validation Schemas
Relay uses Zod for runtime validation. Here are the validation schemas:
fingerprintSchema
const fingerprintSchema = z.object({
clipboardValue: z.string().nullable(),
ipAddress: z.string().nullable(),
deviceManufacturer: z.string().nullable(),
deviceModel: z.string().nullable(),
osName: z.string().nullable(),
osVersion: z.string().nullable(),
screenWidth: z.number(),
screenHeight: z.number(),
pixelRatio: z.number(),
timeZone: z.string().nullable(),
languageTags: z.array(z.string()),
});captureRequestSchema
const captureRequestSchema = z.object({
deferredLinkUrl: z.string().min(1),
fingerprint: fingerprintSchema,
});processRequestSchema
const processRequestSchema = fingerprintSchema;Error Handling
Client Errors
The client throws RelayError instances:
try {
await client.capture(url);
} catch (error) {
if (error instanceof RelayError) {
console.error("Relay error:", error.message);
}
}Common error messages:
"Capture request failed with status 404": Server endpoint not found"Capture request failed with status 500": Server error"Process request failed with status 404": Server endpoint not found"Process request failed with status 500": Server error
Server Errors
The server returns standard HTTP error responses:
400 Bad Request:
{
"error": "Validation error message"
}404 Not Found:
{
"error": "Not Found"
}500 Internal Server Error:
{
"error": "Internal server error"
}Utility Functions
generateHash()
Generate a SHA-256 hash from a string.
export function generateHash(payload: string): stringParameters:
payload: String to hash
Returns: Hex-encoded SHA-256 hash
Example:
import { generateHash } from "@korsolutions/relay";
const hash = generateHash("some-data");
console.log(hash); // "abc123..."Constants
Default Hash Function
The default fingerprint hashing function uses SHA-256:
const defaultHashFingerprint = async (data: Fingerprint): Promise<string> => {
const stringToHash = JSON.stringify({
ipAddress: data.ipAddress,
deviceManufacturer: data.deviceManufacturer,
deviceModel: data.deviceModel,
osName: data.osName,
osVersion: data.osVersion,
screenWidth: data.screenWidth,
screenHeight: data.screenHeight,
pixelRatio: data.pixelRatio,
timeZone: data.timeZone,
languageTags: data.languageTags,
clipboardValue: data.clipboardValue,
});
return generateHash(stringToHash);
};Default Expiry
Deferred links default to 7 days expiry (if implemented in your storage):
const DEFAULT_EXPIRY_DAYS = 7;TypeScript Support
Relay is written in TypeScript and provides full type definitions. Types are exported from both entry points:
// Server-side types
import type {
Fingerprint,
FingerprintDbRecord,
DeferredLink,
RelayConfig,
ProcessResponse,
} from "@korsolutions/relay";
// Client-side types (also includes server types)
import type {
RelayExpoClient,
RelayClientOptions,
} from "@korsolutions/relay/client";