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

@ketrics/sdk-frontend

v0.3.1

Published

Ketrics SDK for tenant application frontends

Downloads

78

Readme

Ketrics Frontend SDK

TypeScript SDK for tenant application frontends running within the Ketrics platform. Provides authentication token management, context retrieval, and secure communication with the parent Ketrics application.

Overview

Purpose

The @ketrics/sdk-frontend package enables tenant applications deployed as iframes within the Ketrics platform to manage authentication tokens and retrieve execution context. Its core responsibility is bridging the authentication gap between sandboxed tenant applications and the parent Ketrics window, which holds the actual authentication state.

Key responsibilities:

  • Token Access: Read JWT access tokens stored in localStorage by the CDN loader bootstrap script
  • Context Retrieval: Provide tenant ID, user ID, application ID, and Data Plane API URL context
  • Token Lifecycle Management: Monitor token expiration and trigger refresh requests when tokens approach expiry
  • Parent Communication: Establish secure postMessage communication channel with the parent Ketrics window for token refresh events

Architectural Position

The SDK operates within a multi-layer iframe architecture:

┌─────────────────────────────────────────────────────────────────┐
│                    Ketrics Frontend (LaunchPage.tsx)            │
│  Manages auth state, refreshes tokens, controls iframe lifecycle │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                         iframe                            │  │
│  │  ┌─────────────────────────────────────────────────────┐  │  │
│  │  │  CDN loader.html (bootstrap)                        │  │  │
│  │  │     └─ Receives AUTH_TOKEN from parent              │  │  │
│  │  │     └─ Stores context in localStorage               │  │  │
│  │  │     └─ Redirects to tenant app                      │  │  │
│  │  └─────────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────────┐  │  │
│  │  │  Tenant Application (uses @ketrics/sdk-frontend)    │  │  │
│  │  │     ├─ reads context from localStorage              │  │  │
│  │  │     ├─ makes API calls with retrieved token         │  │  │
│  │  │     ├─ requests token refresh via postMessage       │  │  │
│  │  │     └─ receives token updates via postMessage       │  │  │
│  │  └─────────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

The SDK specifically handles the "Tenant Application" layer, maintaining no direct coupling to the parent window beyond postMessage events.

Boundaries

The SDK does NOT handle:

  • Initial authentication or login flows
  • Token issuance or validation
  • Storage persistence beyond browser localStorage
  • CORS or API gateway configuration

Business Logic

Problem Solved

Tenant applications are deployed as iframes with sandboxed origins from a CDN, preventing direct access to the parent window's authentication state. However, these applications require authenticated access to the Data Plane API on behalf of the logged-in user. This SDK solves this by:

  1. Decoupling authentication: Tenant apps don't need to understand the parent's auth mechanism
  2. Lazy token delivery: Tokens are pushed to tenant apps only when needed via postMessage
  3. Proactive refresh: SDK automatically monitors expiration and requests refresh before tokens become invalid
  4. Event-driven updates: Token updates from parent are received asynchronously via postMessage

Core Workflows

Initialization Workflow

1. User launches tenant app from Ketrics UI
2. LaunchPage loads iframe with CDN loader.html?target=<app_url>
3. Loader sends LOADER_READY → parent window
4. Parent sends AUTH_TOKEN with full context → loader
5. Loader stores context in localStorage using ketrics_* keys
6. Loader navigates iframe to tenant application URL
7. Tenant app initializes SDK: auth = createAuthManager()
8. Tenant app starts auto-refresh: auth.initAutoRefresh(config)
9. SDK reads context from localStorage, sets up message listener and 10s expiration check interval

Token Refresh Workflow

1. SDK's 10-second check detects token expiring within buffer window (default 60s)
2. SDK sends REQUEST_TOKEN_REFRESH → parent window via postMessage
3. Parent receives message, validates request origin is iframe, calls refreshAuth()
4. Parent backend issues new token with updated expiry
5. Parent sends TOKEN_UPDATE → iframe with new token and expiry
6. SDK receives TOKEN_UPDATE, validates message source, stores in localStorage
7. SDK invokes onTokenUpdated callback with new token
8. Tenant app updates HTTP client headers with fresh token

Business Rules Implemented

Token Expiration Checks:

  • isTokenExpiringSoon(bufferSeconds): Returns true if expiry - now ≤ bufferSeconds * 1000 milliseconds
  • isTokenExpired(): Returns true if now ≥ expiry timestamp
  • Default buffer is 60 seconds; prevents requests with tokens that would expire mid-request

Auto-Refresh Mechanism:

  • Interval-based checking every 10 seconds (not event-driven) for robustness
  • Immediate check on initAutoRefresh() call to catch already-expired tokens
  • Fire-and-forget requests; parent window handles retry via next interval check
  • Only one auto-refresh cycle active at a time; calling initAutoRefresh() twice stops first cycle

Storage Isolation:

  • All operations wrapped in try-catch; missing/unavailable localStorage returns null
  • Each localStorage key is namespaced with ketrics_ prefix to avoid conflicts
  • TOKEN_RECEIVED_AT key reserved for future analytics but not currently used

Input/Output Expectations

Input to SDK:

  • AuthConfig object with optional refreshBuffer (seconds, default 60) and two callbacks
  • Message events from parent window containing { type, accessToken, accessTokenExpiry } payload
  • localStorage key-value pairs populated by CDN loader

Output from SDK:

  • Return values: string (tokens), number (timestamp), boolean (expiry checks), KetricsContext object
  • Side effects: localStorage writes, postMessage events to parent, console logs
  • Callbacks invoked: onTokenUpdated(string) when parent pushes new token, onRefreshError(Error) when postMessage fails

Data Contract Example:

// Input: TOKEN_UPDATE from parent
{
  type: 'TOKEN_UPDATE',
  accessToken: 'eyJhbGciOiJIUzI1NiIs...',
  accessTokenExpiry: 1738794600000  // Unix ms
}

// Output: KetricsContext to tenant app
{
  accessToken: 'eyJhbGciOiJIUzI1NiIs...',
  accessTokenExpiry: 1738794600000,
  tenantId: 'tenant-uuid',
  userId: 'user-uuid',
  applicationId: 'app-uuid',
  runtimeApiUrl: 'https://api.ketrics.io/v1'
}

Edge Cases Handled

Missing/Invalid Data:

  • Missing access token: getAccessToken() returns null, downstream code must check
  • Invalid timestamp format in localStorage: parseInt() with radix 10 prevents octal interpretation
  • Missing KetricsContext values: Each getter returns null independently, not entire context

Storage Failures:

  • localStorage disabled/unavailable: All getters/setters wrapped in try-catch, fail gracefully
  • localStorage quota exceeded: setItem() failures logged but don't break token update flow

Lifecycle Issues:

  • Multiple initAutoRefresh() calls: Previous interval and event listener properly cleaned up
  • stopAutoRefresh() called before initAutoRefresh(): No-op, safe operation
  • clearTokens() called during active auto-refresh: Stops refresh first, then clears

Communication Issues:

  • Parent window unreachable (cross-origin blocked): postMessage() fails silently in most browsers, error caught and logged
  • Message from wrong origin: postMessage uses wildcard '*' origin (consider security implications in deployment)
  • TOKEN_UPDATE with missing accessToken field: Update skipped, old token remains valid

Technical Details

Technology Stack and Dependencies

Language & Compilation:

  • TypeScript 5.x (target: ES2020, module: ES2020)
  • Compiles to standard ES modules with CommonJS interoperability
  • Full type safety with strict mode enabled

Runtime Environment:

  • Target browsers: ES2020 support (Chrome 80+, Firefox 74+, Safari 14+, Edge 80+)
  • Requires DOM APIs: localStorage, window.parent.postMessage(), setInterval/clearInterval, addEventListener/removeEventListener
  • Node.js 24.0.0+ for build/development

Build & Distribution:

  • TypeScript compiler (tsc) for build
  • npm for package management
  • Published to npm registry as public scoped package @ketrics/sdk-frontend
  • Distributed as ES modules with TypeScript declaration files

Zero Runtime Dependencies: The SDK has no npm dependencies, only devDependencies (TypeScript). This minimizes bundle size and security surface.

File Structure

ketrics-sdk-frontend/
├── src/
│   ├── index.ts          # Package entry point, public API exports
│   ├── auth.ts           # AuthManager class, core token management logic
│   └── types.ts          # TypeScript interfaces, constants, storage key definitions
├── dist/                 # Compiled JavaScript and declaration files (build output)
├── node_modules/         # Development dependencies
├── package.json          # Package manifest, npm scripts, metadata
├── tsconfig.json         # TypeScript compiler configuration
├── .npmignore            # Files excluded from npm package (src, dist source maps, tests)
└── README.md             # Documentation

File Responsibilities:

  • index.ts (43 lines): Public module API. Exports AuthManager class, createAuthManager() factory, type definitions (AuthConfig, KetricsContext), and constants (STORAGE_KEYS, MESSAGE_TYPES). Single entry point for all consumers.

  • auth.ts (255 lines): AuthManager class implementing token lifecycle management. Private state: checkTimer (interval ID), config (AuthConfig), messageHandler (bound event listener). Public methods for token access, expiration checking, refresh coordination, and auto-refresh lifecycle. Private handleMessage() method implements TOKEN_UPDATE protocol.

  • types.ts (59 lines): TypeScript interface definitions and constant exports. AuthConfig interface defines refresh buffer and two optional callbacks. KetricsContext interface describes complete authentication/execution context. STORAGE_KEYS const object maps all localStorage keys. MESSAGE_TYPES const object defines postMessage event types. Enables type-safe integration with parent window.

Key Functions/Classes and Purposes

AuthManager Class

Constructor/Lifecycle:

// Private state initialized
private checkTimer: ReturnType<typeof setInterval> | null = null;
private config: AuthConfig = {};
private messageHandler: ((event: MessageEvent) => void) | null = null;

Token Access Methods:

  • getAccessToken(): Returns current JWT from ketrics_access_token localStorage key or null
  • getAccessTokenExpiry(): Returns Unix millisecond timestamp from ketrics_access_token_expiry key, parsed as base-10 integer
  • isTokenExpiringSoon(bufferSeconds = 60): Boolean check: expiry - Date.now() <= bufferSeconds * 1000
  • isTokenExpired(): Boolean check: Date.now() >= expiry

Context Access Methods:

  • getContext(): Returns KetricsContext object aggregating all stored values; always succeeds with nulls where values missing
  • getTenantId(), getUserId(), getApplicationId(), getRuntimeApiUrl(): Individual localStorage key accessors

Refresh Coordination:

  • requestRefresh(): Sends { type: 'REQUEST_TOKEN_REFRESH' } via window.parent.postMessage(..., '*'). Logs success, catches and reports errors via onRefreshError callback
  • initAutoRefresh(config): Orchestrates complete auto-refresh setup. Stops existing refresh first, stores config, binds message handler, starts 10s interval loop, checks immediately. Logs [Ketrics Auth] Auto-refresh initialized
  • stopAutoRefresh(): Clears interval, removes event listener, nullifies references. Safe to call multiple times
  • clearTokens(): Stops auto-refresh, then removes all ketrics_* keys from localStorage. Final cleanup on logout

Internal Message Handler:

  • handleMessage(event): Checks for TOKEN_UPDATE message type. If contains accessToken, stores both token and expiry to localStorage. Invokes onTokenUpdated callback if registered. All errors caught and logged

Factory Function:

export function createAuthManager(): AuthManager {
  return new AuthManager();
}

Type Definitions

AuthConfig:

interface AuthConfig {
  refreshBuffer?: number;
  onTokenUpdated?: (accessToken: string) => void;
  onRefreshError?: (error: Error) => void;
}

KetricsContext:

interface KetricsContext {
  accessToken: string | null;
  accessTokenExpiry: number | null;
  tenantId: string | null;
  userId: string | null;
  applicationId: string | null;
  runtimeApiUrl: string | null;
}

Constants:

STORAGE_KEYS = {
  ACCESS_TOKEN: 'ketrics_access_token',
  ACCESS_TOKEN_EXPIRY: 'ketrics_access_token_expiry',
  TENANT_ID: 'ketrics_tenant_id',
  USER_ID: 'ketrics_user_id',
  APPLICATION_ID: 'ketrics_application_id',
  RUNTIME_API_URL: 'ketrics_runtime_api_url',
  TOKEN_RECEIVED_AT: 'ketrics_token_received_at',
}

MESSAGE_TYPES = {
  REQUEST_TOKEN_REFRESH: 'REQUEST_TOKEN_REFRESH',
  TOKEN_UPDATE: 'TOKEN_UPDATE',
}

Configuration Options and Environment Variables

AuthConfig.refreshBuffer:

  • Type: number (seconds)
  • Default: 60 seconds
  • Controls how early before token expiry to request refresh
  • Set higher (e.g., 300) for APIs with slow response times; set lower for minimal token lifetime
  • Applied to all expiration checks once configured

AuthConfig.onTokenUpdated:

  • Type: (accessToken: string) => void
  • Invoked whenever parent sends TOKEN_UPDATE with valid accessToken
  • Recommended use: Update HTTP client default headers (e.g., axios.defaults.headers.common['Authorization'])
  • Executes synchronously after localStorage write; errors don't prevent operation

AuthConfig.onRefreshError:

  • Type: (error: Error) => void
  • Invoked if postMessage fails (rare; typically cross-origin issues)
  • Errors are pre-converted to Error objects
  • Recommended use: Log to monitoring/analytics system

No Environment Variables: SDK reads no environment variables. All configuration is runtime API. localStorage keys are hardcoded constants.

External Integrations

Parent Window (Ketrics Frontend)

Protocol: Cross-origin postMessage

Outbound Messages from SDK:

window.parent.postMessage(
  { type: 'REQUEST_TOKEN_REFRESH' },
  '*'  // Wildcard origin
)

Inbound Messages to SDK:

{
  type: 'TOKEN_UPDATE',
  accessToken: 'eyJhbGc...',
  accessTokenExpiry: 1738794600000
}

Expectations:

  • Parent window listens for REQUEST_TOKEN_REFRESH messages
  • Parent validates request origin is iframe (SDK doesn't; uses wildcard)
  • Parent calls backend to refresh token
  • Parent sends TOKEN_UPDATE with fresh token
  • Parent sends updates proactively when token changes outside SDK request (optional but recommended)

Data Plane API

Integration Point: Tenant application uses runtimeApiUrl from context

Usage Pattern:

const { runtimeApiUrl, tenantId, applicationId } = auth.getContext();
const token = auth.getAccessToken();

fetch(`${runtimeApiUrl}/tenants/${tenantId}/applications/${applicationId}/functions/getData`, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}` },
  body: JSON.stringify(...)
})

API Expectations:

  • Base URL in runtimeApiUrl (e.g., https://api.ketrics.io/v1)
  • Accepts Bearer token authentication
  • Endpoints follow pattern: /tenants/{id}/applications/{id}/functions/{name}
  • Returns 401 Unauthorized if token invalid/expired

CDN Loader (loader.html)

Upstream Integration: SDK reads data populated by loader

Loader Responsibilities:

  • Receives AUTH_TOKEN message from parent window
  • Stores all context values in localStorage under ketrics_* keys
  • Navigates iframe to tenant application URL
  • Sets TOKEN_RECEIVED_AT timestamp (currently unused by SDK)

Contract: SDK assumes all ketrics_* keys already populated when createAuthManager() called. No validation of key format or values.

Data Flow

Initialization Sequence

1. CDN loader writes to localStorage:
   ketrics_access_token = "eyJ..."
   ketrics_access_token_expiry = "1738794600000"
   ketrics_tenant_id = "tenant-uuid"
   ketrics_user_id = "user-uuid"
   ketrics_application_id = "app-uuid"
   ketrics_runtime_api_url = "https://api.ketrics.io/v1"

2. Tenant app imports SDK:
   import { createAuthManager } from '@ketrics/sdk-frontend'

3. Tenant app instantiates:
   const auth = createAuthManager()
   // AuthManager object created, private state initialized

4. Tenant app configures auto-refresh:
   auth.initAutoRefresh({
     refreshBuffer: 60,
     onTokenUpdated: (token) => {
       axios.defaults.headers.common['Authorization'] = `Bearer ${token}`
     }
   })
   // Message listener attached
   // 10s interval started
   // Immediate expiration check performed
   // If expired/expiring, REQUEST_TOKEN_REFRESH sent

5. Tenant app reads context:
   const { runtimeApiUrl, tenantId } = auth.getContext()
   // Reads from localStorage, returns aggregated object

Token Access Flow

Tenant App                    AuthManager                  localStorage
    │                              │                            │
    │ getAccessToken()             │                            │
    │ ─────────────────────────────>                            │
    │                              │ getItem('ketrics_...')     │
    │                              │ ──────────────────────────>
    │                              │ <──────────────────────────
    │                              │ 'eyJ...' (string)          │
    │ <─────────────────────────────                            │
    │ token: 'eyJ...'              │                            │

Token used in API request:
    │ fetch(url, {                 │                            │
    │   headers: {                 │                            │
    │     'Authorization': `Bearer ${token}`                    │
    │   }                          │                            │
    │ })                           │                            │

Token Refresh Flow

Time    AuthManager              Parent Window              localStorage
 │
 │      (10s interval check)
 ├─ isTokenExpiringSoon() → true (expiry < now + 60s)
 │
 ├─ postMessage({ type: 'REQUEST_TOKEN_REFRESH' })
 │──────────────────────────────────>
 │                                   (parent receives message)
 │                                   refreshAuth() called
 │                                   backend issues new token
 │                                   <── new token received
 │
 │      <──────────────────────────
 │      postMessage({ type: 'TOKEN_UPDATE', accessToken: '...', accessTokenExpiry: ... })
 │
 ├─ handleMessage() invoked
 │
 ├─ setItem('ketrics_access_token', newToken)
 ├─ setItem('ketrics_access_token_expiry', newExpiry)  ──────> localStorage updated
 │
 ├─ onTokenUpdated(newToken) callback invoked
 │      (tenant app updates axios headers, etc.)
 │
 └─ (next interval check at current + 10s)

Data Transformations

Token Expiry Format:

  • Stored: Unix timestamp in milliseconds (number converted to string in localStorage)
  • Retrieved: parseInt(storedValue, 10) back to number
  • Compared: expiry - Date.now() (both milliseconds, comparison valid)
  • Transmitted: Parent sends as number in TOKEN_UPDATE payload; SDK stores as string

No Cryptographic Operations: Tokens are opaque strings; SDK performs no validation, decoding, or signature verification. Parent window and backend responsible for token security.

No Transformations on Context: KetricsContext values returned as-is from localStorage; no type coercion or normalization applied.

State Mutations

AuthManager Private State Changes:

  • initAutoRefresh() sets: checkTimer, config, messageHandler
  • handleMessage() modifies: localStorage only (external)
  • stopAutoRefresh() clears: checkTimer, messageHandler
  • clearTokens() removes: all localStorage ketrics_* keys

External State Changes:

  • localStorage writes: 2 keys per TOKEN_UPDATE (token + expiry)
  • Message events: 1 REQUEST_TOKEN_REFRESH per refresh cycle
  • Event listeners: 1 message handler registered/removed per init/stop cycle

Error Handling

Error Scenarios and Handling

| Scenario | Handler | Outcome | |----------|---------|---------| | localStorage disabled/unavailable | try-catch in all getters/setters | Returns null / operation silent fails | | postMessage call fails | try-catch in requestRefresh() | Error logged, onRefreshError callback invoked | | TOKEN_UPDATE missing accessToken field | Guard check in handleMessage() | Update skipped, old token remains | | Invalid expiry format in localStorage | parseInt() with radix 10 | Returns NaN, isTokenExpiringSoon() treats as expired | | Multiple initAutoRefresh() calls | stopAutoRefresh() called first | Previous timers/listeners cleaned up | | Parent window unreachable | postMessage() fails silently (browser security) | Error caught and logged, callback invoked on next check attempt | | Message origin not validated | Uses wildcard '*' | Accepts messages from any origin (vulnerability if strict validation needed) |

Retry Logic and Fallback Mechanisms

Auto-Refresh Retry Strategy:

  • Not event-driven; uses 10-second polling interval
  • If parent doesn't respond to REQUEST_TOKEN_REFRESH, next interval (10s later) will check again
  • No exponential backoff or maximum retry limit
  • SDK assumes parent window always available (or will recover)

Token Expiration Fallback:

  • If token already expired when app starts: initAutoRefresh() detects and immediately requests refresh
  • If refresh takes > 60s (default buffer): tenant app might make requests with expired token; parent/API returns 401
  • Tenant app responsible for handling 401 responses (retry with fresh token or redirect to login)

Missing Context Fallback:

  • If localStorage keys not populated by loader: getters return null
  • Tenant app must check for null before using context values
  • SDK provides no alternative fallback (no hardcoded defaults)

Logging Approach

Console Logging: All logs prefixed with [Ketrics Auth] for easy filtering in browser DevTools

Log Levels and Messages:

Info level (console.log):

[Ketrics Auth] Auto-refresh initialized
[Ketrics Auth] Requested token refresh from parent
[Ketrics Auth] Token updated from parent
[Ketrics Auth] Auto-refresh stopped
[Ketrics Auth] Tokens cleared

Error level (console.error):

[Ketrics Auth] Failed to request token refresh: {error message}
[Ketrics Auth] Failed to store updated token: {error message}

No Logging of Sensitive Data: Logs don't include actual tokens, keys, or user IDs (security-conscious)

Log Accessibility: Console logs only appear in browser developer tools; no server-side logging by SDK

Application Responsibility: Tenant app should wrap SDK callbacks to implement custom logging/monitoring

Usage

Installation

npm install @ketrics/sdk-frontend

Requires Node.js 24.0.0 or higher. Compatible with any frontend framework (React, Vue, Angular, vanilla JS).

Basic Setup

Minimal Example:

import { createAuthManager } from '@ketrics/sdk-frontend';

const auth = createAuthManager();
auth.initAutoRefresh();

// Token available immediately (if loader populated localStorage)
const token = auth.getAccessToken();

Recommended Setup with Callbacks:

import axios from 'axios';
import { createAuthManager } from '@ketrics/sdk-frontend';

const auth = createAuthManager();

auth.initAutoRefresh({
  refreshBuffer: 60,  // Request refresh 60 seconds before expiry
  onTokenUpdated: (newToken) => {
    // Update HTTP client headers with fresh token
    axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
    console.log('Token refreshed and client updated');
  },
  onRefreshError: (error) => {
    // Log refresh failures to monitoring system
    console.error('Token refresh failed:', error);
    // Optionally trigger user re-authentication
  },
});

// Create axios instance with initial token
const token = auth.getAccessToken();
if (token) {
  axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}

export { auth, axios };

Example Invocations

Reading Context and Making API Calls:

async function fetchUserData() {
  const context = auth.getContext();

  // Check context available
  if (!context.runtimeApiUrl || !context.tenantId) {
    throw new Error('Authentication context not initialized');
  }

  const token = auth.getAccessToken();
  if (!token) {
    throw new Error('No access token available');
  }

  const response = await fetch(
    `${context.runtimeApiUrl}/tenants/${context.tenantId}/data`,
    {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
    }
  );

  if (response.status === 401) {
    // Token expired or invalid; wait for auto-refresh or clear and re-authenticate
    auth.clearTokens();
    throw new Error('Token invalid, please refresh');
  }

  return response.json();
}

Checking Token Expiration:

// Check if refresh is needed soon (within 2 minutes)
if (auth.isTokenExpiringSoon(120)) {
  console.warn('Token will expire soon; manual refresh recommended');
}

// Check if token already expired
if (auth.isTokenExpired()) {
  console.error('Token has expired; clearing and requiring re-login');
  auth.clearTokens();
  window.location.reload();
}

// Get exact expiry timestamp for UI display
const expiry = auth.getAccessTokenExpiry();
if (expiry) {
  const expiresAt = new Date(expiry);
  console.log(`Token expires at: ${expiresAt.toISOString()}`);
}

Component Lifecycle Integration (React Example):

import { useEffect, useRef } from 'react';
import { createAuthManager } from '@ketrics/sdk-frontend';

function MyComponent() {
  const authRef = useRef(null);

  useEffect(() => {
    // Initialize on mount
    authRef.current = createAuthManager();
    authRef.current.initAutoRefresh({
      refreshBuffer: 60,
      onTokenUpdated: (token) => {
        // Update HTTP client or state
        console.log('Token updated');
      },
    });

    // Cleanup on unmount
    return () => {
      authRef.current?.stopAutoRefresh();
    };
  }, []);

  return <div>{/* component content */}</div>;
}

Cleanup and Logout:

function handleLogout() {
  // Stop auto-refresh and clear all tokens
  auth.clearTokens();

  // Redirect to login or parent window
  window.parent.postMessage({ type: 'LOGOUT' }, '*');
  // or redirect
  // window.location.href = '/login';
}

Testing Approach

Unit Testing (recommended tools: Jest, Vitest with jsdom):

  1. Token Access Methods: Mock localStorage, test getters return values or null

    test('getAccessToken returns token from localStorage', () => {
      localStorage.setItem('ketrics_access_token', 'test-token');
      const auth = new AuthManager();
      expect(auth.getAccessToken()).toBe('test-token');
    });
  2. Expiration Checks: Mock Date.now(), test boundary conditions

    test('isTokenExpiringSoon with buffer', () => {
      const now = Date.now();
      const expiryIn30s = now + 30 * 1000;
      localStorage.setItem('ketrics_access_token_expiry', expiryIn30s.toString());
    
      const auth = new AuthManager();
      expect(auth.isTokenExpiringSoon(60)).toBe(true);  // Expires < 60s away
      expect(auth.isTokenExpiringSoon(20)).toBe(false); // Expires > 20s away
    });
  3. postMessage Handling: Mock window.parent.postMessage, listen for calls

    test('requestRefresh sends postMessage', () => {
      const spy = jest.spyOn(window.parent, 'postMessage');
      const auth = new AuthManager();
      auth.requestRefresh();
      expect(spy).toHaveBeenCalledWith(
        { type: 'REQUEST_TOKEN_REFRESH' },
        '*'
      );
    });
  4. Message Events: Send synthetic MessageEvent, verify localStorage updates

    test('TOKEN_UPDATE updates localStorage', () => {
      const auth = new AuthManager();
      auth.initAutoRefresh();
    
      const event = new MessageEvent('message', {
        data: {
          type: 'TOKEN_UPDATE',
          accessToken: 'new-token',
          accessTokenExpiry: 9999999999999,
        },
      });
      window.dispatchEvent(event);
    
      expect(localStorage.getItem('ketrics_access_token')).toBe('new-token');
    });
  5. Auto-Refresh Lifecycle: Verify timers started/stopped, event listeners added/removed

    test('stopAutoRefresh clears timer and removes listener', () => {
      const auth = new AuthManager();
      auth.initAutoRefresh();
    
      const clearIntervalSpy = jest.spyOn(global, 'clearInterval');
      const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
    
      auth.stopAutoRefresh();
    
      expect(clearIntervalSpy).toHaveBeenCalled();
      expect(removeEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function));
    });

Integration Testing (test with loader simulation):

  1. Simulate CDN loader populating localStorage
  2. Load tenant app and initialize SDK
  3. Verify auth manager reads context correctly
  4. Simulate parent postMessage events
  5. Verify SDK responds with refresh requests and callback invocations

Manual Testing Checklist:

  • [ ] Launch app, verify initial token loaded from localStorage
  • [ ] Check browser console for [Ketrics Auth] Auto-refresh initialized
  • [ ] Wait past refresh buffer, verify REQUEST_TOKEN_REFRESH sent to parent
  • [ ] Verify onTokenUpdated callback invoked when parent sends TOKEN_UPDATE
  • [ ] Test clearTokens() removes all ketrics_* keys
  • [ ] Test stopAutoRefresh() stops polling (no more logs every 10s)
  • [ ] Test with localStorage disabled (private browsing); verify graceful null returns

Development and Build

Development Workflow:

cd ketrics-sdk-frontend

# Install dependencies
npm install

# Build TypeScript to dist/
npm run build

# Watch mode for development
npm run dev

# Clean build artifacts
npm run clean

Build Output:

  • dist/index.js - Compiled module entry point
  • dist/auth.js - Compiled AuthManager class
  • dist/types.js - Compiled type definitions
  • dist/index.d.ts - TypeScript declarations (public API)
  • dist/auth.d.ts - AuthManager type definitions
  • dist/types.d.ts - Type definitions
  • .map files - Source maps for debugging

TypeScript Configuration:

  • Target: ES2020 (modern browser support)
  • Module: ES2020 (native ES modules)
  • Strict: true (type safety)
  • Declaration: true (generate .d.ts files)
  • Source maps enabled for debugging

Publishing

Version Management:

# Bump patch version (0.2.0 → 0.2.1)
npm version patch

# Bump minor version (0.2.0 → 0.3.0)
npm version minor

# Bump major version (0.2.0 → 1.0.0)
npm version major

Manual Publishing:

# Authenticate with npm
npm login

# Build and publish to npm registry
npm run build
npm publish --access public

# Verify published
npm view @ketrics/sdk-frontend version

Automated Publishing (via GitHub Actions):

  1. Workflow file: publish-sdk-frontend.yml
  2. Trigger: Manual dispatch via GitHub Actions UI (workflow_dispatch)
  3. Steps:
    • Checkout repository
    • Install dependencies
    • Run TypeScript build (validates code)
    • Publish to npm registry with --access public

Pre-publish Checklist:

  • [ ] Increment version in package.json
  • [ ] Update CHANGELOG (if maintained)
  • [ ] Run npm run build locally and verify no errors
  • [ ] Run tests if available
  • [ ] Verify package.json files array includes dist/
  • [ ] Verify .npmignore excludes src/ and node_modules
  • [ ] Commit and push changes
  • [ ] Create git tag matching version
  • [ ] Trigger publish workflow or run npm publish

Summary

The Ketrics Frontend SDK is a minimal, focused authentication token management library for tenant applications deployed in iframes within the Ketrics platform. It provides zero-dependency token access, expiration monitoring, and parent-window communication via a clean TypeScript API. The SDK assumes the parent window and CDN loader handle initial token delivery and refresh orchestration, making it a thin integration layer rather than a complete authentication system. All operations are localStorage-backed and postMessage-coordinated, ensuring compatibility with iframe security boundaries.