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

@tiquo/dom-package

v1.3.2

Published

Tiquo SDK for third-party websites - authentication, customer profiles, orders, bookings, and enquiries

Readme

@tiquo/dom-package

Tiquo SDK for third-party websites. Integrate authentication, customer profiles, orders, bookings, and enquiries into your website with a simple JavaScript API.

Features

  • Email OTP Authentication - Secure passwordless login using email verification codes
  • JWT-Based Tokens - Secure access and refresh tokens with automatic refresh
  • Multi-Tab Sync - Auth state automatically syncs across all browser tabs
  • Native App Integration - WebView token injection for iOS/Android hybrid apps
  • Customer Flow Integration - Embed authenticated customer flows with one line of code
  • Phone Number Utilities - Validation, formatting, and country code support for 200+ countries
  • Framework Agnostic - Works with React, Vue, Svelte, or vanilla JavaScript
  • TypeScript Support - Full type definitions included

Installation

npm install @tiquo/dom-package
# or
yarn add @tiquo/dom-package
# or
pnpm add @tiquo/dom-package

Quick Start

1. Get Your Public Key

Go to your Tiquo dashboard → Settings → Auth DOM to get your public key.

2. Initialize the SDK

import { TiquoAuth } from '@tiquo/dom-package';

const auth = new TiquoAuth({
  publicKey: 'pk_dom_your_public_key'
});

3. Authenticate Users

// Step 1: Send OTP to user's email
await auth.sendOTP('[email protected]');

// Step 2: Verify the OTP code
const result = await auth.verifyOTP('[email protected]', '123456');

// Step 3: Get user data
const session = await auth.getUser();
console.log(session?.user.email); // [email protected]
console.log(session?.customer?.firstName); // John

API Reference

Constructor

const auth = new TiquoAuth({
  publicKey: string;       // Required: Your public key from Tiquo dashboard
  apiEndpoint?: string;    // Optional: API endpoint (defaults to production)
  storagePrefix?: string;  // Optional: localStorage key prefix
  debug?: boolean;         // Optional: Enable debug logging
  enableTabSync?: boolean; // Optional: Multi-tab session sync (default: true)
});

Methods

sendOTP(email: string): Promise<SendOTPResult>

Send a verification code to the user's email.

const result = await auth.sendOTP('[email protected]');
// { success: true, message: 'OTP sent' }

verifyOTP(email: string, otp: string): Promise<VerifyOTPResult>

Verify the OTP code and authenticate the user.

const result = await auth.verifyOTP('[email protected]', '123456');
// {
//   success: true,
//   accessToken: 'eyJhbGciOiJSUzI1NiJ9...',
//   refreshToken: 'rt_xxx...',
//   expiresIn: 3600,
//   expiresAt: 1234567890000,
//   isNewUser: false,
//   hasCustomer: true
// }

getUser(): Promise<TiquoSession | null>

Get the current authenticated user and customer data.

const session = await auth.getUser();
if (session) {
  console.log(session.user.id);
  console.log(session.user.email);
  console.log(session.customer?.firstName);
  console.log(session.customer?.customerNumber);
}

isAuthenticated(): boolean

Check if the user is currently authenticated.

if (auth.isAuthenticated()) {
  // User is logged in
}

updateProfile(updates): Promise<ProfileUpdateResult>

Update the authenticated customer's profile. Only allows updating the logged-in customer's own data.

Phone numbers are automatically normalized to E.164 format before being sent to the API. You can pass common formats and they'll be cleaned up, but for best results include the country code.

const result = await auth.updateProfile({
  firstName: 'John',
  lastName: 'Doe',
  displayName: 'Johnny',
  phone: '+1 (555) 456-7890', // Auto-normalized to +15554567890
});

console.log(result.customer.firstName); // 'John'

Available fields:

  • firstName - Customer's first name
  • lastName - Customer's last name
  • displayName - Display name (nickname)
  • phone - Primary phone number (E.164 format recommended: +[country code][number])
  • profilePhoto - URL to profile photo
  • phones - Full phone list (array of { number, isPrimary, order })

Important: Phone numbers should include a country code (e.g., +1 for US, +44 for UK). Use TiquoPhone.buildPhone() to construct properly formatted numbers from a country selector + national number input. See Phone Number Utilities below.

logout(): Promise<void>

Log out the current user.

await auth.logout();

getOrders(options?): Promise<GetOrdersResult>

Get the authenticated customer's order history. Only returns orders for the logged-in customer.

// Get all orders (default limit 50)
const { orders, hasMore, nextCursor } = await auth.getOrders();

// With pagination
const page1 = await auth.getOrders({ limit: 10 });
const page2 = await auth.getOrders({ limit: 10, cursor: page1.nextCursor });

// Filter by status
const completedOrders = await auth.getOrders({ status: 'completed' });

Options:

  • limit - Number of orders to return (max 100, default 50)
  • cursor - Order ID to start after (for pagination)
  • status - Filter by status: draft, pending, processing, completed, cancelled, refunded, open_tab

Returns:

  • orders - Array of order objects with items, totals, and status
  • hasMore - Whether there are more orders to fetch
  • nextCursor - Cursor for the next page (if hasMore is true)

getBookings(options?): Promise<GetBookingsResult>

Get the authenticated customer's booking history. Only returns bookings for the logged-in customer.

// Get all bookings (default limit 50)
const { bookings, hasMore, nextCursor } = await auth.getBookings();

// Get upcoming bookings only (sorted soonest first)
const upcomingBookings = await auth.getBookings({ upcoming: true });

// Filter by status
const confirmedBookings = await auth.getBookings({ status: 'confirmed' });

// With pagination
const page1 = await auth.getBookings({ limit: 10 });
const page2 = await auth.getBookings({ limit: 10, cursor: page1.nextCursor });

Options:

  • limit - Number of bookings to return (max 100, default 50)
  • cursor - Booking ID to start after (for pagination)
  • status - Filter by status: draft, scheduled, confirmed, reminder_sent, waiting_room, waiting_list, checked_in, active, in_progress, completed, cancelled, no_show, rescheduled
  • upcoming - If true, only return future bookings (sorted soonest first)

Returns:

  • bookings - Array of booking objects with service details, date/time, and status
  • hasMore - Whether there are more bookings to fetch
  • nextCursor - Cursor for the next page (if hasMore is true)

getEnquiries(options?): Promise<GetEnquiriesResult>

Get the authenticated customer's enquiry history. Only returns enquiries for the logged-in customer.

// Get all enquiries (default limit 50)
const { enquiries, hasMore, nextCursor } = await auth.getEnquiries();

// Filter by status
const openEnquiries = await auth.getEnquiries({ status: 'in_progress' });

// With pagination
const page1 = await auth.getEnquiries({ limit: 10 });
const page2 = await auth.getEnquiries({ limit: 10, cursor: page1.nextCursor });

Options:

  • limit - Number of enquiries to return (max 100, default 50)
  • cursor - Enquiry ID to start after (for pagination)
  • status - Filter by status: new, in_progress, waiting_customer, waiting_internal, won, lost, closed, resolved, archived

Returns:

  • enquiries - Array of enquiry objects with subject, message, status, and priority
  • hasMore - Whether there are more enquiries to fetch
  • nextCursor - Cursor for the next page (if hasMore is true)

destroy(): void

Clean up resources when destroying the auth instance. Call this when your component unmounts or when you no longer need the auth instance.

// In React
useEffect(() => {
  const auth = new TiquoAuth({ publicKey: 'pk_dom_xxx' });
  
  return () => {
    auth.destroy(); // Cleanup on unmount
  };
}, []);

// Or when done with auth
auth.destroy();

onAuthStateChange(callback): () => void

Subscribe to authentication state changes. Returns an unsubscribe function.

const unsubscribe = auth.onAuthStateChange((session) => {
  if (session) {
    console.log('User logged in:', session.user.email);
  } else {
    console.log('User logged out');
  }
});

// Later: unsubscribe()

embedCustomerFlow(flowUrl, container, options?): Promise<HTMLIFrameElement>

Embed a Tiquo customer flow with automatic authentication.

await auth.embedCustomerFlow(
  'https://book.tiquo.app/your-flow',
  '#container',
  {
    width: '100%',
    height: '600px',
    onLoad: () => console.log('Flow loaded'),
    onError: (err) => console.error('Flow error:', err)
  }
);

getIframeToken(customerFlowId?): Promise<IframeTokenResult>

Generate a short-lived token for manual iframe authentication.

const { token, expiresAt } = await auth.getIframeToken();
// Use token in iframe URL: ?_auth_token=xxx

React Integration

import { TiquoAuth } from '@tiquo/dom-package';
import { useState, useEffect } from 'react';

const auth = new TiquoAuth({ publicKey: 'pk_dom_xxx' });

function App() {
  const [session, setSession] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Subscribe to auth changes
    const unsubscribe = auth.onAuthStateChange((s) => {
      setSession(s);
      setLoading(false);
    });

    return unsubscribe;
  }, []);

  if (loading) return <div>Loading...</div>;

  if (!session) {
    return <LoginForm />;
  }

  return (
    <div>
      <p>Welcome, {session.user.email}!</p>
      <button onClick={() => auth.logout()}>Logout</button>
    </div>
  );
}

function LoginForm() {
  const [email, setEmail] = useState('');
  const [otp, setOtp] = useState('');
  const [step, setStep] = useState<'email' | 'otp'>('email');
  const [error, setError] = useState('');

  const handleSendOTP = async (e) => {
    e.preventDefault();
    try {
      await auth.sendOTP(email);
      setStep('otp');
    } catch (err) {
      setError(err.message);
    }
  };

  const handleVerifyOTP = async (e) => {
    e.preventDefault();
    try {
      await auth.verifyOTP(email, otp);
      // Auth state change will update the UI
    } catch (err) {
      setError(err.message);
    }
  };

  if (step === 'email') {
    return (
      <form onSubmit={handleSendOTP}>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="Email"
          required
        />
        <button type="submit">Send Code</button>
        {error && <p>{error}</p>}
      </form>
    );
  }

  return (
    <form onSubmit={handleVerifyOTP}>
      <p>Enter the code sent to {email}</p>
      <input
        type="text"
        value={otp}
        onChange={(e) => setOtp(e.target.value)}
        placeholder="000000"
        maxLength={6}
        required
      />
      <button type="submit">Verify</button>
      {error && <p>{error}</p>}
    </form>
  );
}

Vue Integration

<script setup>
import { TiquoAuth } from '@tiquo/dom-package';
import { ref, onMounted, onUnmounted } from 'vue';

const auth = new TiquoAuth({ publicKey: 'pk_dom_xxx' });

const session = ref(null);
const loading = ref(true);
let unsubscribe;

onMounted(() => {
  unsubscribe = auth.onAuthStateChange((s) => {
    session.value = s;
    loading.value = false;
  });
});

onUnmounted(() => {
  unsubscribe?.();
  auth.destroy(); // Clean up resources including tab sync
});

async function logout() {
  await auth.logout();
}
</script>

<template>
  <div v-if="loading">Loading...</div>
  <div v-else-if="session">
    <p>Welcome, {{ session.user.email }}!</p>
    <button @click="logout">Logout</button>
  </div>
  <LoginForm v-else />
</template>

Phone Number Utilities

The SDK includes TiquoPhone, a static utility class for working with international phone numbers. It supports 200+ countries and handles validation, normalization, formatting, and country detection — everything you need to build a phone input with a country selector.

Import

import { TiquoPhone } from '@tiquo/dom-package';

Get Countries (for building a dropdown)

const countries = TiquoPhone.getCountries();
// Returns alphabetically sorted array:
// [
//   { code: "AF", name: "Afghanistan", dialCode: "+93", format: "XX XXX XXXX", flag: "🇦🇫" },
//   ...
//   { code: "GB", name: "United Kingdom", dialCode: "+44", format: "XXXX XXXXXX", flag: "🇬🇧" },
//   { code: "US", name: "United States", dialCode: "+1", format: "(XXX) XXX-XXXX", flag: "🇺🇸" },
//   ...
// ]

Build a Phone Number (country selector + input)

// Combine a country selector value with a local number input
TiquoPhone.buildPhone("GB", "07911 123456"); // → "+447911123456"
TiquoPhone.buildPhone("US", "(555) 456-7890"); // → "+15554567890"
TiquoPhone.buildPhone("FR", "06 65 08 50 44"); // → "+33665085044"

Validate a Phone Number

TiquoPhone.validate("+44 7911 123456");
// → { valid: true, normalized: "+447911123456", countryCode: "GB" }

TiquoPhone.validate("555-1234");
// → { valid: false, reason: "Phone number must include a country code..." }

TiquoPhone.validate("+999 123");
// → { valid: false, reason: "Phone number is too short..." }

Normalize to E.164

TiquoPhone.normalize("+1 (555) 456-7890");    // → "+15554567890"
TiquoPhone.normalize("+44 (0)20 3227 4972");   // → "+442032274972"
TiquoPhone.normalize("0033 6 65 08 50 44");    // → "+33665085044"
TiquoPhone.normalize("+49 0151 12345678");     // → "+4915112345678"

Format for Display

TiquoPhone.format("+15554567890");  // → "+1 (555) 456-7890"
TiquoPhone.format("+447911123456"); // → "+44 7911 123456"
TiquoPhone.format("+33665085044");  // → "+33 6 65 08 50 44"

Detect Country

TiquoPhone.detectCountry("+15554567890");  // → "US"
TiquoPhone.detectCountry("+447911123456"); // → "GB"
TiquoPhone.detectCountry("+33665085044");  // → "FR"

Get Dial Code

TiquoPhone.getDialCode("US"); // → "+1"
TiquoPhone.getDialCode("GB"); // → "+44"
TiquoPhone.getDialCode("FR"); // → "+33"

Example: Phone Input with Country Selector (React)

import { TiquoPhone } from '@tiquo/dom-package';
import { useState } from 'react';

function PhoneInput({ onChange }) {
  const countries = TiquoPhone.getCountries();
  const [country, setCountry] = useState('US');
  const [number, setNumber] = useState('');
  const [error, setError] = useState('');

  const handleChange = (newCountry, newNumber) => {
    setCountry(newCountry);
    setNumber(newNumber);

    if (!newNumber.trim()) {
      setError('');
      onChange('');
      return;
    }

    const phone = TiquoPhone.buildPhone(newCountry, newNumber);
    if (!phone) {
      setError('Invalid phone number');
      onChange('');
      return;
    }

    const result = TiquoPhone.validate(phone);
    if (result.valid) {
      setError('');
      onChange(result.normalized);
    } else {
      setError(result.reason);
      onChange('');
    }
  };

  return (
    <div>
      <select
        value={country}
        onChange={(e) => handleChange(e.target.value, number)}
      >
        {countries.map((c) => (
          <option key={c.code} value={c.code}>
            {c.flag} {c.name} ({c.dialCode})
          </option>
        ))}
      </select>
      <input
        type="tel"
        value={number}
        onChange={(e) => handleChange(country, e.target.value)}
        placeholder={countries.find(c => c.code === country)?.format}
      />
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
}

// Usage with updateProfile:
function ProfilePage() {
  const [phone, setPhone] = useState('');

  const handleSave = async () => {
    await auth.updateProfile({ phone }); // Already in E.164 format
  };

  return (
    <div>
      <PhoneInput onChange={setPhone} />
      <button onClick={handleSave} disabled={!phone}>Save</button>
    </div>
  );
}

Multi-Tab Session Sync

The SDK automatically synchronizes authentication state across all browser tabs using the BroadcastChannel API. This means:

  • Login in one tab → All tabs are authenticated
  • Logout in one tab → All tabs are logged out
  • Profile updates sync across tabs

This feature is enabled by default and works automatically. No additional code is required.

Disabling Tab Sync

If you need to disable multi-tab sync (e.g., for isolated sessions):

const auth = new TiquoAuth({
  publicKey: 'pk_dom_xxx',
  enableTabSync: false, // Disable multi-tab sync
});

Browser Support

Multi-tab sync requires BroadcastChannel support. It's available in all modern browsers:

  • Chrome 54+
  • Firefox 38+
  • Safari 15.4+
  • Edge 79+

For older browsers, the feature gracefully degrades - auth still works normally, just without cross-tab sync.

Error Handling

All SDK methods can throw TiquoAuthError with helpful error codes:

import { TiquoAuth, TiquoAuthError } from '@tiquo/dom-package';

try {
  await auth.verifyOTP(email, otp);
} catch (error) {
  if (error instanceof TiquoAuthError) {
    switch (error.code) {
      case 'OTP_VERIFY_FAILED':
        console.log('Invalid or expired code');
        break;
      case 'NOT_AUTHENTICATED':
        console.log('Please log in first');
        break;
      default:
        console.log('Error:', error.message);
    }
  }
}

Native App WebView Integration

When building hybrid apps with iOS/Android native + WebView, you can pass authentication tokens from the native app to the DOM package.

Method 1: Config Parameters

const auth = new TiquoAuth({
  publicKey: 'pk_dom_xxx',
  accessToken: 'eyJhbGciOiJSUzI1NiJ9...',  // From OAuth flow
  refreshToken: 'rt_xxx...'
});

Method 2: Global Variable (Native App Injects JS)

iOS (Swift):

let script = """
window.__TIQUO_INIT_TOKEN__ = {
  accessToken: '\(accessToken)',
  refreshToken: '\(refreshToken)'
};
"""
let userScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: true)
webView.configuration.userContentController.addUserScript(userScript)

Android (Kotlin):

webView.evaluateJavascript("""
  window.__TIQUO_INIT_TOKEN__ = {
    accessToken: '$accessToken',
    refreshToken: '$refreshToken'
  };
""".trimIndent(), null)

Method 3: URL Fragment

https://yoursite.com/page#access_token=eyJ...&refresh_token=rt_...

The DOM package automatically detects and consumes tokens from all three methods. The user will be immediately authenticated in the WebView without needing to log in again.

For complete integration examples, see the Client API documentation.

Token Management

The SDK uses JWT tokens for authentication:

  • Access Token: Short-lived (1 hour), used for API requests
  • Refresh Token: Long-lived (30 days), used to obtain new access tokens
  • Automatic Refresh: Tokens are automatically refreshed before expiration
// Get tokens for manual use (advanced)
const accessToken = auth.getAccessToken();
const refreshToken = auth.getRefreshToken();

// Initialize with external tokens
auth.initWithTokens(accessToken, refreshToken);

Security Considerations

  • The public key is safe to expose in client-side code
  • Tokens are stored in localStorage with automatic refresh
  • Access tokens expire after 1 hour (automatically refreshed)
  • Refresh tokens expire after 30 days
  • OTP codes expire after 10 minutes
  • Failed OTP attempts are rate limited (max 5 attempts per code)

Support

License

MIT