npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@explorins/pers-sdk

v1.6.47

Published

Platform-agnostic SDK for PERS (Phygital Experience Rewards System) - Core business logic and API integration

Readme

@explorins/pers-sdk

npm version License: MIT

Platform-agnostic TypeScript SDK for PERS (Phygital Experience Rewards System) - Modern Manager-Service architecture providing clean, consistent APIs for tourism loyalty applications.


Table of Contents


Overview

PERS (Phygital Experience Rewards System) is a comprehensive tourism loyalty platform bridging physical and digital experiences. It provides:

  • Loyalty Programs: Token-based rewards, credit systems, and user tiers
  • Campaign Management: Marketing campaigns with automated rewards distribution
  • Business Network: Multi-tenant business discovery and partnerships
  • Blockchain Integration: EVM-compatible chains (Ethereum, Polygon, Camino)
  • Redemption Engine: Reward fulfillment with optional blockchain signing
  • Analytics: Real-time transaction and user engagement metrics

Dependencies

Required Peer Dependencies

These packages MUST be installed alongside @explorins/pers-sdk:

npm install @explorins/pers-shared ethers@^6.15.0

| Package | Version | Purpose | |---------|---------|---------| | @explorins/pers-shared | * | Shared types, interfaces, and DTOs used across all SDK domains | | ethers | ^6.15.0 | Blockchain/Web3 operations (required for sdk.web3 manager) |

Optional Dependencies (Platform-Specific)

| Package | Required For | Notes | |---------|--------------|-------| | rxjs | AngularHttpClientAdapter only | Loaded dynamically at runtime; NOT bundled into SDK |

AI Agent Note: The SDK has ZERO runtime RxJS dependency unless you specifically use AngularHttpClientAdapter. Browser, Node.js, and React Native integrations do NOT require RxJS.

Direct Dependencies

| Package | Purpose | |---------|---------| | @explorins/web3-ts | Internal Web3 chain utilities |


Installation

# Core SDK
npm install @explorins/pers-sdk

# Required peer dependencies
npm install @explorins/pers-shared ethers@^6.15.0

# Optional: For Angular applications only
npm install rxjs

Quick Start

Minimum Viable Example

import { PersSDK } from '@explorins/pers-sdk';
import { BrowserFetchClientAdapter } from '@explorins/pers-sdk/platform-adapters';

// 1. Initialize SDK (required: adapter + project key)
const sdk = new PersSDK(new BrowserFetchClientAdapter(), {
  apiProjectKey: 'your-project-key'  // Get from PERS dashboard
});

// 2. Authenticate with external JWT (Firebase, Auth0, Cognito, etc.)
const externalJWT = await yourAuthProvider.getIdToken();  // Your JWT
const authResult = await sdk.auth.loginWithToken(externalJWT, 'user');
console.log('Authenticated user:', authResult.user.id);

// 3. Use SDK managers
const campaigns = await sdk.campaigns.getActiveCampaigns();
const tokens = await sdk.tokens.getTokens();

Complete Example

import { PersSDK } from '@explorins/pers-sdk';
import { BrowserFetchClientAdapter } from '@explorins/pers-sdk/platform-adapters';

// Initialize SDK with browser fetch adapter
const sdk = new PersSDK(new BrowserFetchClientAdapter(), {
  environment: 'production',
  apiProjectKey: 'your-project-key'
});

// Authentication - login with external JWT (Firebase, Auth0, etc.)
const authResult = await sdk.auth.loginWithToken(firebaseJWT, 'user');
const user = await sdk.auth.getCurrentUser();

// Business operations
const businesses = await sdk.businesses.getActiveBusinesses();
const business = await sdk.businesses.getBusinessById('business-123');

// Campaign management
const campaigns = await sdk.campaigns.getActiveCampaigns();
const claim = await sdk.campaigns.claimCampaign({ 
  campaignId: 'campaign-123',
  businessId: 'business-456'
});

// Token operations
const tokens = await sdk.tokens.getTokens();
const creditToken = await sdk.tokens.getActiveCreditToken();
const rewardTokens = await sdk.tokens.getRewardTokens();

// User claims history
const userClaims = await sdk.campaigns.getUserClaims();

Platform Integration

Browser / React / Vue

No additional dependencies required.

import { PersSDK } from '@explorins/pers-sdk';
import { BrowserFetchClientAdapter } from '@explorins/pers-sdk/platform-adapters';

const sdk = new PersSDK(new BrowserFetchClientAdapter(), {
  environment: 'production',
  apiProjectKey: 'your-project-key'
});

// Ready to use
const campaigns = await sdk.campaigns.getActiveCampaigns();

Angular

Requires rxjs peer dependency.

npm install rxjs
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { createPersSDK, PersSDK } from '@explorins/pers-sdk';
import { AngularHttpClientAdapter } from '@explorins/pers-sdk/platform-adapters';
import { IndexedDBTokenStorage } from '@explorins/pers-sdk/core';

@Injectable({ providedIn: 'root' })
export class PersSDKService {
  private readonly sdk: PersSDK;

  constructor() {
    const httpClient = inject(HttpClient);
    // Use createPersSDK factory or new PersSDK()
    this.sdk = createPersSDK(
      new AngularHttpClientAdapter(httpClient),
      {
        environment: 'production',
        apiProjectKey: 'your-project-key',
        authStorage: new IndexedDBTokenStorage()  // Recommended
      }
    );
  }

  // Expose managers directly for clean component access
  get auth() { return this.sdk.auth; }
  get users() { return this.sdk.users; }
  get tokens() { return this.sdk.tokens; }
  get businesses() { return this.sdk.businesses; }
  get campaigns() { return this.sdk.campaigns; }
  get redemptions() { return this.sdk.redemptions; }
  get transactions() { return this.sdk.transactions; }
  get purchases() { return this.sdk.purchases; }
  get web3() { return this.sdk.web3; }
  // ... other managers as needed
}

Node.js

No additional dependencies required.

import { PersSDK } from '@explorins/pers-sdk';
import { NodeHttpClientAdapter } from '@explorins/pers-sdk/platform-adapters';

const sdk = new PersSDK(new NodeHttpClientAdapter(), {
  environment: 'production',
  apiProjectKey: 'your-project-key'
});

// Server-side operations
const businesses = await sdk.businesses.getActiveBusinesses();

React Native

Use the dedicated React Native SDK (includes passkeys, secure storage, DPoP):

npm install @explorins/pers-sdk-react-native @react-native-async-storage/async-storage
// In your root layout (e.g., _layout.tsx for Expo Router)
import { PersSDKProvider } from '@explorins/pers-sdk-react-native';

export default function RootLayout() {
  return (
    <PersSDKProvider config={{ apiProjectKey: 'your-project-key' }}>
      <YourApp />
    </PersSDKProvider>
  );
}

// In your components - use hooks
import { useAuth, useTokens, useCampaigns } from '@explorins/pers-sdk-react-native';

function MyComponent() {
  const { login, isAuthenticated } = useAuth();
  const { getTokens } = useTokens();
  const { getActiveCampaigns, claimCampaign } = useCampaigns();
  // ...
}

See @explorins/pers-sdk-react-native for full setup including passkey configuration.


Architecture

The SDK uses a clean Manager-Service pattern with three access levels:

// ═══════════════════════════════════════════════════════════════════════════
// MANAGER LAYER (Recommended - High-level, intuitive APIs)
// ═══════════════════════════════════════════════════════════════════════════

sdk.auth           // Authentication & sessions
sdk.users          // User profiles & management
sdk.userStatus     // User status/tier management
sdk.tokens         // Token types & configuration
sdk.businesses     // Business operations
sdk.campaigns      // Marketing campaigns
sdk.redemptions    // Reward redemptions
sdk.transactions   // Transaction history
sdk.purchases      // Purchase/payment processing
sdk.analytics      // Reporting & insights
sdk.tenants        // Multi-tenant configuration
sdk.donations      // Charitable giving
sdk.files          // File upload/download operations
sdk.web3           // Blockchain operations
sdk.apiKeys        // API key management (Admin)

// ═══════════════════════════════════════════════════════════════════════════
// SERVICE LAYER (Advanced - Full domain access)
// ═══════════════════════════════════════════════════════════════════════════

sdk.campaigns.getCampaignService()    // Full CampaignService access
sdk.tokens.getTokenService()          // Full TokenService access
sdk.businesses.getBusinessService()   // Full BusinessService access
sdk.purchases.getPurchaseService()    // Full PaymentService access
// ... each manager exposes its underlying service

// ═══════════════════════════════════════════════════════════════════════════
// API LAYER (Expert - Direct REST API access)
// ═══════════════════════════════════════════════════════════════════════════

const apiClient = sdk.api();
const customData = await apiClient.get<CustomType>('/custom-endpoint');
await apiClient.post('/custom-endpoint', { data: 'value' });

Available Managers Reference

| Manager | Accessor | Primary Use Cases | |---------|----------|-------------------| | AuthManager | sdk.auth | Login, logout, token management, authentication status | | UserManager | sdk.users | User profiles, account management | | UserStatusManager | sdk.userStatus | User tiers, status levels | | TokenManager | sdk.tokens | Token types, credit tokens, reward tokens | | BusinessManager | sdk.businesses | Business discovery, details, types | | CampaignManager | sdk.campaigns | Campaign discovery, claiming, user history | | RedemptionManager | sdk.redemptions | Redeem rewards, redemption history | | TransactionManager | sdk.transactions | Transaction history, details | | PurchaseManager | sdk.purchases | Payment intents, purchase tokens | | AnalyticsManager | sdk.analytics | Reporting, transaction analytics | | TenantManager | sdk.tenants | Tenant config, client settings | | DonationManager | sdk.donations | Donation types, charitable giving | | FileManager | sdk.files | Signed URLs, media optimization | | Web3Manager | sdk.web3 | Blockchain operations, token metadata | | ApiKeyManager | sdk.apiKeys | API key CRUD (Admin only) |


Core Features

Event System

The SDK provides a platform-agnostic event system for subscribing to SDK-wide events. All events have a userMessage field ready for UI display.

// Subscribe to ALL events - one handler
const unsubscribe = sdk.events.subscribe((event) => {
  showNotification(event.userMessage, event.level);
});

// Filter by domain and level
sdk.events.subscribe((event) => {
  logToSentry(event);
}, { level: 'error' });

// Only transaction successes
sdk.events.subscribe((event) => {
  playSuccessSound();
  confetti();
}, { domain: 'transaction', level: 'success' });

// One-time event (auto-unsubscribe)
sdk.events.once((event) => {
  console.log('First event:', event.type);
});

// Cleanup
unsubscribe();

Event Domains

| Domain | Manager | Events | |--------|---------|--------| | authentication | sdk.auth | LOGIN_SUCCESS | | user | sdk.users | PROFILE_UPDATED | | business | sdk.businesses | BUSINESS_CREATED, BUSINESS_UPDATED, MEMBERSHIP_UPDATED | | campaign | sdk.campaigns | CAMPAIGN_CLAIMED | | redemption | sdk.redemptions | REDEMPTION_COMPLETED | | transaction | sdk.transactions | TRANSACTION_CREATED, TRANSACTION_SUBMITTED |

Event Types

import type { 
  PersEvent,      // Base event type (discriminated union)
  SuccessEvent,   // Success events (business domains)
  ErrorEvent,     // Error events (all domains including technical)
  EventFilter,    // Filter for subscriptions
  EventHandler    // Handler type (sync or async)
} from '@explorins/pers-sdk/core';

// Event structure
interface PersEvent {
  id: string;           // Unique event ID
  timestamp: number;    // Unix timestamp (ms)
  domain: string;       // Event domain
  type: string;         // Event type within domain
  level: 'success' | 'error';
  userMessage: string;  // Ready for UI display
  action?: string;      // Suggested action
  code?: string;        // Backend error code
  details?: object;     // Additional data for logging
}

Async Handler Support

Event handlers can be synchronous or asynchronous. Async errors are caught automatically:

sdk.events.subscribe(async (event) => {
  await saveToDatabase(event);  // Async operations supported
  await sendAnalytics(event);
});

Authentication

The SDK uses external JWT tokens for authentication. You provide a JWT from your authentication provider (Firebase, Auth0, Cognito, etc.), and the SDK exchanges it for PERS access tokens.

// Step 1: Get JWT from your auth provider (Firebase example)
import { getAuth } from 'firebase/auth';
const firebaseUser = getAuth().currentUser;
const externalJWT = await firebaseUser?.getIdToken();

// Step 2: Exchange JWT for PERS tokens
const authResult = await sdk.auth.loginWithToken(externalJWT, 'user');
console.log('User authenticated:', authResult.user.name);

// Admin login
const adminResult = await sdk.auth.loginWithToken(adminJWT, 'admin');
console.log('Admin authenticated:', adminResult.admin.email);

// Check authentication status (async - verifies with server)
const isAuth = await sdk.auth.isAuthenticated();

// Quick check if tokens exist locally (faster, less reliable)
const hasTokens = await sdk.auth.hasValidAuth();

// Get current user
const user = await sdk.auth.getCurrentUser();

// Logout / clear auth
await sdk.auth.clearAuth();

How it works:

  1. Your app authenticates users via Firebase/Auth0/etc.
  2. You call sdk.auth.loginWithToken(jwt) with that JWT
  3. PERS validates the JWT and returns PERS-specific access/refresh tokens
  4. SDK stores tokens and handles automatic refresh
  5. All subsequent SDK calls are automatically authenticated

Security Features:

  • DPoP (Demonstrating Proof-of-Possession): Enabled by default - binds tokens to client
  • Automatic token refresh and validation
  • Flexible storage strategies (LocalStorage, IndexedDB, Memory)
  • Support for user and admin authentication flows

Business Management

// Get all active businesses
const businesses = await sdk.businesses.getActiveBusinesses();

// Get business by ID
const business = await sdk.businesses.getBusinessById('business-123');

// Get business types
const types = await sdk.businesses.getBusinessTypes();

Campaign System

// Get active campaigns available for claiming
const campaigns = await sdk.campaigns.getActiveCampaigns();

// Get campaign details
const campaign = await sdk.campaigns.getCampaignById('campaign-123');

// Claim campaign rewards
const claim = await sdk.campaigns.claimCampaign({
  campaignId: 'campaign-123',
  businessId: 'business-456'  // Optional: associated business
});

// Get user's campaign claim history
const userClaims = await sdk.campaigns.getUserClaims();

Token Management

// Get all token types
const tokens = await sdk.tokens.getTokens();

// Get active credit token (main loyalty currency)
const creditToken = await sdk.tokens.getActiveCreditToken();

// Get reward tokens
const rewards = await sdk.tokens.getRewardTokens();

// Get status tokens (tier/achievement tokens)
const statusTokens = await sdk.tokens.getStatusTokens();

// Get token by blockchain contract
const token = await sdk.tokens.getTokenByContract('0x123...', 'token-id');

User Token Balances (On-Chain)

Important Architecture Note: The PERS backend stores token definitions (contract addresses, ABIs, metadata), but does NOT store user token balances. User balances are queried directly from the blockchain via RPC calls using sdk.web3.* methods.

Data Flow:

  1. sdk.tokens.* → Get token definitions (contract address, ABI, chainId) from PERS backend
  2. sdk.auth.getCurrentUser() → Get user's wallet address
  3. sdk.web3.* → Query blockchain directly for user's token balance

Points Balance (ERC-20)

// Step 1: Get the credit token definition from PERS backend
const creditToken = await sdk.tokens.getActiveCreditToken();

// Step 2: Get user's wallet address
const user = await sdk.auth.getCurrentUser();
const walletAddress = user.wallets?.[0]?.address;

// Step 3: Query blockchain directly for balance
const balance = await sdk.web3.getTokenBalance({
  accountAddress: walletAddress,
  contractAddress: creditToken.contractAddress,
  abi: creditToken.abi,  // Raw ABI from backend works directly
  chainId: creditToken.chainId,
  tokenId: ''  // Empty for ERC-20
});

console.log('Points balance:', balance.balance);
console.log('Has balance:', balance.hasBalance);

Stamps Collection (ERC-721 / ERC-1155)

// Step 1: Get status token definitions (stamps/achievements)
const statusTokens = await sdk.tokens.getStatusTokens();

// Step 2: Get user's wallet address  
const user = await sdk.auth.getCurrentUser();
const walletAddress = user.wallets?.[0]?.address;

// Step 3: Query blockchain for user's stamp collection
for (const stampToken of statusTokens) {
  const collection = await sdk.web3.getTokenCollection({
    accountAddress: walletAddress,
    contractAddress: stampToken.contractAddress,
    abi: stampToken.abi,  // Raw ABI from backend works directly
    chainId: stampToken.chainId,
    maxTokens: 50  // Optional: limit results
  });

  // Filter to tokens with balance > 0
  const ownedStamps = collection.tokens.filter(t => t.hasBalance && t.balance > 0);
  
  console.log(`Stamps from ${stampToken.symbol}:`, ownedStamps.length);
  
  // Each stamp includes metadata (name, description, image, etc.)
  for (const stamp of ownedStamps) {
    console.log(`- Token #${stamp.tokenId}: ${stamp.metadata?.name}`);
    console.log(`  Image: ${stamp.metadata?.imageUrl}`);
  }
}

Complete Example: Load All User Balances

async function loadUserTokenBalances(sdk: PersSDK) {
  // Get user wallet
  const user = await sdk.auth.getCurrentUser();
  const walletAddress = user.wallets?.[0]?.address;
  if (!walletAddress) throw new Error('User has no wallet');

  // Get all token definitions
  const [creditToken, statusTokens, rewardTokens] = await Promise.all([
    sdk.tokens.getActiveCreditToken(),
    sdk.tokens.getStatusTokens(),
    sdk.tokens.getRewardTokens()
  ]);

  // Query ERC-20 balance (Points)
  const pointsBalance = creditToken ? await sdk.web3.getTokenBalance({
    accountAddress: walletAddress,
    contractAddress: creditToken.contractAddress,
    abi: creditToken.abi,
    chainId: creditToken.chainId,
    tokenId: ''
  }) : null;

  // Query ERC-721/ERC-1155 collections (Stamps, Rewards)
  const allNftTokens = [...(statusTokens || []), ...(rewardTokens || [])];
  const collections = await Promise.all(
    allNftTokens.map(token => 
      sdk.web3.getTokenCollection({
        accountAddress: walletAddress,
        contractAddress: token.contractAddress,
        abi: token.abi,
        chainId: token.chainId,
        maxTokens: 100
      }).catch(() => null)  // Handle individual failures gracefully
    )
  );

  return {
    points: pointsBalance,
    stamps: collections.filter(Boolean)
  };
}

Transaction Request Building

The SDK provides factory functions for building transaction request DTOs. These are tree-shakeable and provide type-safe transaction creation:

import { 
  buildMintRequest, 
  buildBurnRequest, 
  buildTransferRequest,
  buildPOSTransferRequest,
  buildSubmissionRequest 
} from '@explorins/pers-sdk/transaction';
import { AccountOwnerType } from '@explorins/pers-shared';

// Mint tokens to a recipient
const mintRequest = buildMintRequest({
  amount: 100,
  contractAddress: '0x...',
  chainId: 137,
  recipientAccountId: 'user-123',
  recipientAccountType: AccountOwnerType.USER
});

// Burn tokens
const burnRequest = buildBurnRequest({
  amount: 50,
  contractAddress: '0x...',
  chainId: 137,
  senderAccountId: 'user-123',
  senderAccountType: AccountOwnerType.USER
});

// Transfer tokens between accounts
const transferRequest = buildTransferRequest({
  amount: 100,
  contractAddress: '0x...',
  chainId: 137,
  senderAccountId: 'user-123',
  senderAccountType: AccountOwnerType.USER,
  recipientAccountId: 'business-456',
  recipientAccountType: AccountOwnerType.BUSINESS
});

// Submit the transaction
const result = await sdk.transactions.createTransaction(mintRequest);

POS Transaction Flow

For Point-of-Sale scenarios where a business submits a transaction on behalf of a user, use buildPOSTransferRequest:

import { buildPOSTransferRequest } from '@explorins/pers-sdk/transaction';

// POS flow: User pays business, business authorized to submit
const posRequest = buildPOSTransferRequest({
  amount: 100,
  contractAddress: '0x...',
  chainId: 137,
  userAccountId: 'user-123',      // User sending tokens
  businessAccountId: 'business-456' // Business receiving & authorized to submit
});

// This automatically sets:
// - engagedBusinessId: 'business-456' (for reporting)
// - authorizedSubmitterId: 'business-456' (can submit the signed tx)
// - authorizedSubmitterType: AccountOwnerType.BUSINESS

const result = await sdk.transactions.createTransaction(posRequest);

For custom authorization scenarios, use buildTransferRequest with manual POS fields:

import { buildTransferRequest, type POSAuthorizationOptions } from '@explorins/pers-sdk/transaction';

const customPOSRequest = buildTransferRequest({
  amount: 100,
  contractAddress: '0x...',
  chainId: 137,
  senderAccountId: 'user-123',
  senderAccountType: AccountOwnerType.USER,
  recipientAccountId: 'business-456',
  recipientAccountType: AccountOwnerType.BUSINESS,
  // POS authorization fields
  engagedBusinessId: 'business-456',
  authorizedSubmitterId: 'business-456',
  authorizedSubmitterType: AccountOwnerType.BUSINESS
});

| POS Field | Type | Description | |-----------|------|-------------| | engagedBusinessId | string | Business commercially involved (for stats/reporting) | | authorizedSubmitterId | string | Entity authorized to submit the signed transaction | | authorizedSubmitterType | AccountOwnerType | Type of authorized submitter (USER or BUSINESS) |


### Purchase/Payment Processing

```typescript
// Create payment intent
const intent = await sdk.purchases.createPaymentIntent(
  100,                    // amount
  'usd',                  // currency
  '[email protected]',     // email
  'Token Purchase'        // description
);

// Get user's purchase history
const purchases = await sdk.purchases.getAllUserPurchases();

// Get available purchase tokens
const purchaseTokens = await sdk.purchases.getActivePurchaseTokens();

Configuration

API Project Key (Required)

The apiProjectKey is a tenant-specific identifier that associates your application with a PERS tenant (organization). You must obtain this from the PERS admin dashboard or from your PERS account manager.

// Example project key format (64-character hex string)
apiProjectKey: 'e3e16b5863f0a042b949650d236a37b0758bd51177463d627921112d2291fe01'

Full Configuration Example

import { PersSDK } from '@explorins/pers-sdk';
import { IndexedDBTokenStorage } from '@explorins/pers-sdk/core';
import { BrowserFetchClientAdapter } from '@explorins/pers-sdk/platform-adapters';

const sdk = new PersSDK(new BrowserFetchClientAdapter(), {
  // Environment: 'development' | 'staging' | 'production'
  environment: 'production',  // Default: 'production'
  
  // Project key for API authentication (required)
  apiProjectKey: 'your-project-key',
  
  // API version (currently only v2 supported)
  apiVersion: 'v2',  // Default: 'v2'
  
  // Request timeout in milliseconds
  timeout: 30000,  // Default: 30000
  
  // Retry attempts for failed requests
  retries: 3,  // Default: 3
  
  // Token storage strategy (recommended: IndexedDB)
  authStorage: new IndexedDBTokenStorage(),  // Default: LocalStorage
  
  // DPoP (Demonstrating Proof-of-Possession) configuration
  dpop: {
    enabled: true,  // Default: true (recommended for security)
  },
  
  // Authentication type for auto-created provider
  authType: 'user',  // 'user' | 'admin', Default: 'user'
  
  // Token refresh margin (seconds before expiry to refresh)
  tokenRefreshMargin: 60,  // Default: 60
  
  // Background refresh threshold (seconds)
  backgroundRefreshThreshold: 30  // Default: 30
});

Configuration Options Reference

| Option | Type | Default | Description | |--------|------|---------|-------------| | environment | 'development' \| 'staging' \| 'production' | 'production' | API environment target | | apiProjectKey | string | - | Required. Project key for API authentication | | apiVersion | 'v2' | 'v2' | API version | | timeout | number | 30000 | Request timeout (ms) | | retries | number | 3 | Retry attempts | | authStorage | TokenStorage | LocalStorage | Token storage implementation | | dpop.enabled | boolean | true | Enable DPoP security | | authType | 'user' \| 'admin' | 'user' | Authentication type | | authProvider | PersAuthProvider | auto-created | Custom auth provider (overrides authType) |


Advanced Authentication

Using IndexedDB Storage (Recommended)

For better security and performance, use IndexedDB instead of LocalStorage:

import { PersSDK } from '@explorins/pers-sdk';
import { IndexedDBTokenStorage } from '@explorins/pers-sdk/core';
import { BrowserFetchClientAdapter } from '@explorins/pers-sdk/platform-adapters';

const sdk = new PersSDK(new BrowserFetchClientAdapter(), {
  environment: 'production',
  apiProjectKey: 'your-key',
  authStorage: new IndexedDBTokenStorage()  // Secure, async storage
});

Custom Storage Implementation

Implement the TokenStorage interface for custom storage backends:

import type { TokenStorage } from '@explorins/pers-sdk/core';
import { AUTH_STORAGE_KEYS, DPOP_STORAGE_KEYS } from '@explorins/pers-sdk/core';

class CustomStorage implements TokenStorage {
  // CRITICAL: Set to 'false' for string-only backends (LocalStorage-like)
  // Set to 'true' for object-capable backends (IndexedDB-like)
  readonly supportsObjects = false;

  async get(key: string): Promise<unknown | null> {
    // Use AUTH_STORAGE_KEYS.ACCESS_TOKEN, DPOP_STORAGE_KEYS.PRIVATE, etc.
    return yourBackend.get(key);
  }

  async set(key: string, value: unknown): Promise<void> {
    await yourBackend.set(key, value);
  }

  async remove(key: string): Promise<void> {
    await yourBackend.remove(key);
  }

  async clear(): Promise<void> {
    await yourBackend.clear();
  }
}

Error Handling

The SDK provides structured error types for consistent error handling:

import { 
  PersApiError, 
  AuthenticationError, 
  ErrorUtils 
} from '@explorins/pers-sdk/core';

try {
  const user = await sdk.auth.getCurrentUser();
} catch (error) {
  // Check for specific error types
  if (error instanceof AuthenticationError) {
    // Handle authentication failure (401)
    console.error('Auth failed:', error.message);
    console.error('Endpoint:', error.endpoint);
    console.error('User message:', error.userMessage);
    // Redirect to login...
    
  } else if (error instanceof PersApiError) {
    // Handle general API errors
    console.error('API Error:', error.message);
    console.error('Status:', error.status);
    console.error('Retryable:', error.retryable);
    
  } else {
    // Handle unexpected errors
    console.error('Unexpected error:', error);
  }
}

// Utility functions for error inspection
const status = ErrorUtils.getStatus(error);      // Extract status code
const message = ErrorUtils.getMessage(error);    // Extract error message
const retryable = ErrorUtils.isRetryable(error); // Check if retryable

Error Types Reference

| Error Class | Status | Use Case | |-------------|--------|----------| | PersApiError | Various | General API request failures | | AuthenticationError | 401 | Authentication/authorization failures | | NetworkError | - | Network connectivity issues | | TokenRefreshNeeded | - | Internal: token refresh required | | LogoutRequired | - | Internal: session invalidated |


Bundle Size

  • Core SDK: ~85 KB (minified)
  • Tree-shaking: Fully supported - import only what you need
  • Zero runtime dependencies: No RxJS, no heavy libraries in core
  • Native APIs: Uses browser-native fetch, crypto, IndexedDB

TypeScript Types

All domain interfaces are exported from @explorins/pers-shared. Import types directly for strong typing:

import type {
  // User & Auth
  UserDTO,
  SessionAuthContextResponseDTO,
  
  // Business
  BusinessDTO,
  BusinessTypeDTO,
  
  // Campaigns
  CampaignDTO,
  CampaignClaimDTO,
  CampaignClaimRequestDTO,
  
  // Tokens
  TokenDTO,
  TokenMetadataDTO,
  
  // Transactions
  TransactionDTO,
  
  // Redemptions
  RedemptionDTO,
  RedemptionRedeemDTO,
  
  // Payments
  PaymentIntentDTO,
  PurchaseDTO,
  PurchaseTokenDTO,
  
  // Tenant
  TenantDTO,
  TenantClientConfigDTO
} from '@explorins/pers-shared';

Key Interfaces Reference

| Domain | Interface | Description | |--------|-----------|-------------| | Auth | UserDTO | Authenticated user profile | | Auth | SessionAuthContextResponseDTO | Login response with tokens + user/admin | | Business | BusinessDTO | Business entity with details | | Business | BusinessTypeDTO | Business category/type definition | | Campaign | CampaignDTO | Campaign with rewards and rules | | Campaign | CampaignClaimDTO | User's claim record | | Campaign | CampaignClaimRequestDTO | Request body for claiming | | Token | TokenDTO | Token type definition (credit, reward, status) | | Token | TokenMetadataDTO | On-chain token metadata | | Transaction | TransactionDTO | Transaction record | | Redemption | RedemptionDTO | Redemption offer | | Redemption | RedemptionRedeemDTO | Redemption result | | Payment | PaymentIntentDTO | Stripe payment intent | | Payment | PurchaseDTO | Purchase record | | Tenant | TenantDTO | Tenant configuration |

Usage Example

import { PersSDK } from '@explorins/pers-sdk';
import { BrowserFetchClientAdapter } from '@explorins/pers-sdk/platform-adapters';
import type { CampaignDTO, CampaignClaimRequestDTO } from '@explorins/pers-shared';

const sdk = new PersSDK(new BrowserFetchClientAdapter(), {
  apiProjectKey: 'your-key'
});

// Type-safe campaign operations
const campaigns: CampaignDTO[] = await sdk.campaigns.getActiveCampaigns();

const claimRequest: CampaignClaimRequestDTO = {
  campaignId: campaigns[0].id,
  businessId: 'business-123'
};

const claim = await sdk.campaigns.claimCampaign(claimRequest);

Documentation


Related Packages

| Package | Description | |---------|-------------| | @explorins/pers-sdk-react-native | React Native integration with passkey support | | @explorins/pers-shared | Shared types, interfaces, and DTOs |


License

MIT License