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

@cshah18/sdk

v5.7.0

Published

CoBuy Embedded SDK for browser JavaScript integration

Downloads

1,503

Readme

CoBuy Embedded SDK

A lightweight, production-ready TypeScript SDK for embedding CoBuy's collaborative purchasing widget in your web applications. The SDK exposes a global window.CoBuy object that can be loaded via a <script> tag.

Features

  • 📦 Lightweight: ~7.5 KB (ESM, minified)
  • 🎯 Type-Safe: Full TypeScript support with comprehensive type definitions
  • 🚀 Easy Integration: Simple <script> tag integration
  • ⚙️ Configurable: Support for multiple environments and theme customization
  • 🔧 Well-Tested: ESLint + TypeScript strict mode
  • 📝 Documented: Comprehensive JSDoc comments and examples

Installation

Via Script Tag (Browser)

<script src="https://cdn.cobuy.app/cobuy-sdk.umd.js"></script>
<script>
  window.CoBuy.init({
    merchantKey: "your-merchant-key",
    env: "production",
  });

  window.CoBuy.renderWidget({
    productId: "prod_123",
    container: "#cobuy-widget",
  });
</script>

Via NPM (for Module Bundlers)

npm install @cobuy/sdk
import CoBuy from "@cobuy/sdk";

CoBuy.init({
  merchantKey: "your-merchant-key",
  env: "production",
});

CoBuy.renderWidget({
  productId: "prod_123",
  container: "#cobuy-widget",
});

Quick Start

1. Initialize the SDK

CoBuy.init({
  merchantKey: "your-unique-merchant-key",
  env: "production", // or "local" for localhost:9000
  debug: false,
  theme: {
    primaryColor: "#4f46e5",
    textColor: "#1f2937",
  },
});

2. Render the Widget

CoBuy.renderWidget({
  productId: "prod_123",
  container: "#widget-container", // CSS selector or HTMLElement
  onCheckout: (data) => {
    console.log("Checkout initiated:", data);
  },
  onError: (error) => {
    console.error("Widget error:", error);
  },
  onOpen: () => {
    console.log("Widget opened");
  },
  onClose: () => {
    console.log("Widget closed");
  },
});

API Reference

CoBuy.init(options: CoBuyInitOptions): void

Initializes the SDK with configuration.

Parameters:

interface CoBuyInitOptions {
  merchantKey: string; // Required: Your unique merchant key
  env?: "production" | "local"; // Default: "production" (use "local" for localhost:9000)
  theme?: {
    primaryColor?: string; // e.g., "#4f46e5"
    secondaryColor?: string;
    textColor?: string;
    borderRadius?: string;
    fontFamily?: string;
  };
  debug?: boolean; // Default: false
}

Example:

CoBuy.init({
  merchantKey: "pk_live_abc123xyz",
  env: "production",
  debug: false,
  theme: {
    primaryColor: "#4f46e5",
    borderRadius: "8px",
  },
});

CoBuy.renderWidget(options: RenderWidgetOptions): void

Renders the CoBuy widget into a DOM container.

Parameters:

interface RenderWidgetOptions {
  productId: string; // Required: The product to display
  container: string | HTMLElement; // CSS selector or element
  onCheckout?: (data: { groupId: string; productId: string }) => void;
  onError?: (error: unknown) => void;
  onOpen?: () => void;
  onClose?: () => void;
}

Example:

CoBuy.renderWidget({
  productId: "prod_456",
  container: "#cobuy-widget",
  onCheckout: (data) => {
    console.log(`Group ${data.groupId} checking out product ${data.productId}`);
  },
  onError: (error) => {
    console.error("Checkout failed:", error);
  },
});

CoBuy.version: string

Returns the SDK version.

console.log(CoBuy.version); // "1.0.0"

CoBuy.getApiClient(): ApiClient | null

Returns the initialized API client instance for making direct API calls.

Example:

const apiClient = CoBuy.getApiClient();
if (apiClient) {
  const rewardResponse = await apiClient.getProductReward("prod_123");
  if (rewardResponse.success) {
    console.log("Reward:", rewardResponse.data.reward);
  }
}

ApiClient.getProductReward(productId: string): Promise<ApiResponse<ProductRewardData>>

Retrieves reward information for a specific product.

Response Structure:

interface ProductRewardData {
  productId: string;
  reward: {
    id: string;
    name: string;
    type: "percentage" | "fixed" | "points" | "cashback";
    value: string;
    description: string | null;
    remaining_quantity: number;
    total_quantity: number;
    valid_from: string; // ISO 8601 date
    valid_to: string; // ISO 8601 date
    status: "active" | "inactive" | "expired";
  };
  eligibility: {
    isEligible: boolean;
    reason?: string;
  };
}

Example:

const apiClient = CoBuy.getApiClient();
const response = await apiClient.getProductReward("prod_123");

if (response.success) {
  const { reward, eligibility } = response.data;
  if (eligibility.isEligible) {
    console.log(`${reward.value}% ${reward.name}`);
    console.log(`Valid until: ${reward.valid_to}`);
  }
} else {
  console.error("Error:", response.error.message);
}

API Base URLs

The SDK automatically selects the appropriate API endpoint based on the env parameter:

| Environment | API URL | | ------------ | ------------------------------- | | production | https://api.cobuyza.co.za/api | | local | http://localhost:9000/api |

The backend URLs are managed internally by the SDK for security.

Performance Configuration

The SDK supports optional performance tuning for different network conditions and use cases. Configure these settings when initializing the SDK:

CoBuy.init({
  merchantKey: "your-merchant-key",
  env: "production",
  performance: {
    requestTimeout: 30000, // Default: 30000ms (30 seconds)
    maxRetries: 2, // Default: 2
    animationSpeed: "normal", // Default: "normal" ("fast" | "normal" | "slow")
  },
});

Configuration Options

requestTimeout (number, default: 30000)

Maximum time in milliseconds to wait for API requests before timing out.

Use Cases:

  • Slow Networks (Mobile, poor connections): 60000 or 90000
  • Fast Networks (Fiber, corporate): 10000 or 15000
  • Standard (Default): 30000

Example - Slow Network:

CoBuy.init({
  merchantKey: "your-key",
  performance: {
    requestTimeout: 60000, // Wait up to 60 seconds for API responses
  },
});

maxRetries (number, default: 2)

Number of times to automatically retry failed API requests.

Use Cases:

  • Flaky Networks (WiFi, variable connectivity): 3 or 4
  • Stable Networks (Fiber, datacenter): 1 or 2
  • Offline-First Apps: 0 (handle retries manually)

Example - Flaky Network:

CoBuy.init({
  merchantKey: "your-key",
  performance: {
    maxRetries: 4, // Retry up to 4 times on failure
  },
});

animationSpeed (string, default: "normal")

Controls the speed of widget animations (open/close, transitions, etc).

Options:

  • "fast" - Reduced animation duration for snappy interactions
  • "normal" (default) - Standard animation timing
  • "slow" - Extended animation duration for better visibility

Example - Fast Animations:

CoBuy.init({
  merchantKey: "your-key",
  theme: {
    primaryColor: "#4f46e5",
  },
  performance: {
    animationSpeed: "fast",
  },
});

Complete Example

CoBuy.init({
  merchantKey: "pk_live_abc123xyz",
  env: "production",
  debug: true,
  theme: {
    primaryColor: "#4f46e5",
    borderRadius: "8px",
  },
  performance: {
    requestTimeout: 45000, // 45 seconds for slower connections
    maxRetries: 3, // Retry 3 times on failure
    animationSpeed: "normal", // Standard animation speed
  },
});

Default Behavior

If performance config is not provided, the SDK uses sensible defaults optimized for typical production environments:

{
  requestTimeout: 30000,
  maxRetries: 2,
  animationSpeed: "normal"
}

Error Handling

The SDK uses a standardized error handling approach for different operation types:

Error Patterns by Method Type

1. Initialization & Validation Methods - Throw synchronous errors

  • CoBuy.init() - Throws errors for invalid configuration
  • ConfigManager.setConfig() - Throws errors for validation failure

2. Widget Rendering - Returns Promise that may reject

  • CoBuy.renderWidget() - Returns Promise<void> that rejects on rendering errors
  • Can be handled with try/catch OR .catch()

3. API Methods - Return ApiResponse (no exceptions)

  • apiClient.getProductReward() - Returns {success: true|false, data?, error?}
  • Check response.success to determine outcome

Custom Error Classes

CoBuyNotInitializedError

Thrown when attempting to use SDK features before calling CoBuy.init().

try {
  CoBuy.renderWidget({
    productId: "prod_123",
    container: "#widget",
  });
} catch (error) {
  if (error instanceof CoBuyNotInitializedError) {
    console.error("SDK must be initialized first");
    CoBuy.init({ merchantKey: "your-key" });
  }
}

CoBuyInvalidConfigError

Thrown when invalid configuration is provided to CoBuy.init().

try {
  CoBuy.init({
    authMode: "public",
    // Missing required merchantKey
  });
} catch (error) {
  if (error instanceof CoBuyInvalidConfigError) {
    console.error("Config validation failed:", error.message);
    console.error("Error code:", error.code); // "INVALID_CONFIG"
  }
}

CoBuyRenderError

Thrown when widget rendering fails (container not found, DOM issues, etc).

try {
  await CoBuy.renderWidget({
    productId: "prod_123",
    container: "#nonexistent-container",
  });
} catch (error) {
  if (error instanceof CoBuyRenderError) {
    console.error("Widget render failed:", error.message);
    console.error("Container selector:", error.details?.selector);
  }
}

CoBuyValidationError

Thrown when input validation fails (invalid product ID, invalid container selector, etc).

try {
  await CoBuy.renderWidget({
    productId: "", // Invalid: empty string
    container: "#widget",
  });
} catch (error) {
  if (error instanceof CoBuyValidationError) {
    console.error("Input validation failed:", error.message);
    console.error("Details:", error.details); // { productId: '' }
  }
}

CoBuyApiError

Thrown when API communication fails (network errors, CORS issues, API errors, etc).

const apiClient = CoBuy.getApiClient();
try {
  const response = await apiClient.getProductReward("prod_123");
  if (!response.success) {
    // API returned error response - check error details
    console.error("API Error:", response.error?.code);
    if (response.error?.statusCode === 429) {
      console.log("Rate limited - retry after delay");
    }
  }
} catch (error) {
  // This usually doesn't happen as ApiClient returns ApiResponse
  // But some methods may throw CoBuyApiError
  if (error instanceof CoBuyApiError) {
    console.error("API Error:", error.message);
    console.error("Code:", error.code);
  }
}

Error Base Class

All custom errors inherit from CoBuyError which provides consistent structure:

class CoBuyError extends Error {
  code: string; // Error code for programmatic handling
  details?: unknown; // Additional error details
}

Handling Different Error Types

import {
  CoBuyError,
  CoBuyNotInitializedError,
  CoBuyInvalidConfigError,
  CoBuyRenderError,
  CoBuyValidationError,
  CoBuyApiError,
} from "@cobuy/sdk";

// Universal error handler
async function renderWithErrorHandling() {
  try {
    await CoBuy.renderWidget({
      productId: "prod_123",
      container: "#widget",
    });
  } catch (error) {
    if (error instanceof CoBuyNotInitializedError) {
      // Handle: SDK not initialized
      console.error("Please call CoBuy.init() first");
    } else if (error instanceof CoBuyInvalidConfigError) {
      // Handle: Bad configuration
      console.error("Check your init options:", error.message);
    } else if (error instanceof CoBuyValidationError) {
      // Handle: Invalid input
      console.error("Input validation failed:", error.details);
    } else if (error instanceof CoBuyRenderError) {
      // Handle: Rendering failed
      console.error("Failed to render widget:", error.message);
    } else if (error instanceof CoBuyApiError) {
      // Handle: API communication failed
      console.error("API error:", error.code);
    } else if (error instanceof CoBuyError) {
      // Handle: Other CoBuy errors
      console.error("SDK error:", error.code);
    } else {
      // Handle: Unexpected error
      console.error("Unexpected error:", error);
    }
  }
}

📖 Comprehensive Error Reference: See ERROR_CODES.md for complete list of all error codes, handling strategies, and debugging guidance.

Debugging

Enable debug logging to monitor SDK operations:

CoBuy.init({
  merchantKey: "your-key",
  debug: true, // Enables console logging
});

When debug mode is enabled, the SDK logs:

  • SDK initialization details
  • Environment and API base URL
  • Widget rendering operations

Migration Guide

For breaking changes and upgrade steps, see MIGRATION_GUIDE.md.

Versioning & Changelog

This project follows Semantic Versioning and tracks changes in CHANGELOG.md.

Project Structure

cobuy-sdk/
├── src/
│   ├── index.ts                 # Entry point, window.CoBuy export
│   ├── core/
│   │   ├── cobuy.ts            # Main SDK implementation
│   │   ├── config.ts           # Configuration manager
│   │   ├── logger.ts           # Logging utility
│   │   ├── types.ts            # TypeScript type definitions
│   │   └── errors.ts           # Custom error classes
│   └── ui/
│       └── widget/
│           └── widget-root.ts  # Widget rendering logic
├── dist/                        # Compiled output
│   ├── cobuy-sdk.esm.js        # ES Module format
│   ├── cobuy-sdk.esm.js.map    # Source map
│   ├── cobuy-sdk.umd.js        # UMD format (browser globals)
│   └── cobuy-sdk.umd.js.map    # Source map
├── package.json
├── tsconfig.json
├── rollup.config.mjs
└── eslint.config.js

Development

Prerequisites

  • Node.js 16+
  • npm or yarn

Setup

npm install

Scripts

# Build the SDK
npm run build

# Watch mode (rebuilds on file changes)
npm run build:watch

# Run ESLint
npm run lint

# Fix ESLint issues
npm run lint:fix

# Type check + lint
npm run check

### Versioning (Grunt)

These commands bump the version in package.json, create a commit, and tag the release. Pushing to remote is disabled by default.

```bash
# Patch (x.y.z -> x.y.(z+1))
npm run version:patch

# Minor (x.y.z -> x.(y+1).0)
npm run version:minor

# Major ((x+1).0.0)
npm run version:major

# Prerelease (x.y.z-alpha.0 -> x.y.z-alpha.1)
npm run version:prerelease

# After bumping, push manually if desired
git push && git push --tags

### Code Quality

This project uses:

- **TypeScript**: Strict mode enabled for type safety
- **ESLint**: Enforces code standards
- **Prettier**: Automatic code formatting
- **Rollup**: Module bundler for optimized output

### Building

The SDK is built with Rollup and produces:

1. **ESM Bundle** (`cobuy-sdk.esm.js`): For use with modern module bundlers
2. **UMD Bundle** (`cobuy-sdk.umd.js`): For direct browser inclusion via `<script>` tag

Type definitions are generated automatically and included in the `dist/types/` directory.

## Browser Support

- Modern browsers with ES2017+ support
- Chrome 51+
- Firefox 54+
- Safari 10+
- Edge 15+

## TypeScript Support

The SDK includes full TypeScript definitions. When using with TypeScript:

```typescript
import CoBuy from "@cobuy/sdk";
import type { CoBuySDK, CoBuyInitOptions, RenderWidgetOptions } from "@cobuy/sdk";

// All types are available for import
const options: CoBuyInitOptions = {
  merchantKey: "key",
  env: "production",
};

CoBuy.init(options);

Configuration Examples

Minimal Configuration

CoBuy.init({
  merchantKey: "pk_live_123abc",
});

Full Configuration

CoBuy.init({
  merchantKey: "pk_live_123abc",
  env: "production",
  theme: {
    primaryColor: "#4f46e5",
    secondaryColor: "#e5e7eb",
    textColor: "#1f2937",
    borderRadius: "8px",
    fontFamily: "'Inter', sans-serif",
  },
  debug: true,
});

With Event Handlers

CoBuy.renderWidget({
  productId: "prod_789",
  container: "#my-widget",
  onOpen: () => {
    // Track analytics
    analytics.track("cobuy_widget_opened");
  },
  onCheckout: (data) => {
    // Send to your backend
    api.post("/order/initiate", data);
  },
  onError: (error) => {
    // Handle errors gracefully
    showErrorNotification(error.message);
  },
  onClose: () => {
    // Cleanup
    console.log("Widget closed");
  },
});

Implementation Status

✅ Complete & Stable

  • Core Widget Rendering: Fully implemented with loading states, error handling, and retry logic
  • Product Reward Display: Real-time reward data fetching with TTL-based caching
  • Group Management: Automatic group creation and joining with progress tracking
  • Lobby Modal: Full-featured group coordination UI with sharing, offline redemption, and real-time updates
  • Authentication: Support for both public (merchant key) and token-based auth modes
  • Theme Customization: Comprehensive theming with colors, animations, and typography
  • Accessibility (WCAG AA):
    • Keyboard navigation and focus management
    • Screen reader support with ARIA labels and live regions
    • Semantic HTML and proper heading hierarchy
    • Color contrast compliance
  • Input Validation: Comprehensive validation for colors, product IDs, and auth credentials
  • Request Optimization:
    • Response caching with 60s TTL for product rewards
    • Request deduplication for concurrent identical calls
    • Automatic timeout cleanup to prevent memory leaks
    • Debounced group data refreshes
  • Error Handling: Custom error classes with descriptive messages
  • TypeScript Support: Full type definitions with strict mode enabled
  • Browser Compatibility: ES2017+ support (Chrome 51+, Firefox 54+, Safari 10+)

🟡 Partial Implementation

  • Offline Redemption: QR code generation and manual code entry supported; SMS/email delivery backend integration pending

Known Limitations

Technical Limitations

  1. Bundle Size: ~7.5 KB minified/gzipped (unavoidable due to full modal UI + real-time features)
  2. Single Instance Per Container: Only one widget can render per DOM container; multiple products require multiple containers
  3. Modal Stacking: Only one modal (lobby or group list) can be open simultaneously
  4. Network Timeout Guarantees: Hard timeout of 30 seconds on all API calls; no built-in retry for transient 5xx errors
  5. CSS Scoping: Widget uses CSS custom properties; global CSS may override if specificity is higher

Browser/Platform Limitations

  1. No IE11 Support: SDK targets ES2017+ only
  2. WebSocket Required: Real-time features require WebSocket support (unavailable in some corporate networks)
  3. Third-Party Cookies: Social sharing (WhatsApp, Facebook) requires third-party cookie support
  4. Offline Mode: Limited; QR code works offline, but group sync requires network connectivity

API/Data Limitations

  1. Reward Expiry: Expired rewards show as inactive; no pre-expiry notifications (planned for v2.0)
  2. Group Capacity: Max 999 participants per group (theoretical limit); recommend splitting larger promotions
  3. Concurrency: Widget serializes renders to prevent race conditions; may add concurrent render support in v2.0
  4. Rate Limiting: No built-in request rate limiting client-side (server enforces)

Performance Improvements

  • Lazy-load modal components (reduce initial bundle by ~2 KB)
  • Implement virtual scrolling for activity feeds (currently loads all items)
  • Add service worker for offline caching of static assets

Migration Guide

From Older SDK Versions (v0.x → v1.0)

Breaking Changes

  1. API Client Access

    v0.x:

    const client = window.CoBuy.apiClient;

    v1.0:

    const client = window.CoBuy.getApiClient();
  2. Event Callback Names

    v0.x:

    CoBuy.renderWidget({
      onGroupJoined: (groupId) => {},
      onCheckoutInitiated: (data) => {},
    });

    v1.0:

    CoBuy.renderWidget({
      onCheckout: (data) => {}, // Consolidated
    });
  3. Error Handling

    v0.x:

    try {
      CoBuy.renderWidget({
        /*...*/
      });
    } catch (error) {
      if (error.code === "INVALID_CONFIG") {
      }
    }

    v1.0:

    try {
      CoBuy.renderWidget({
        /*...*/
      });
    } catch (error) {
      if (error instanceof CoBuyInvalidConfigError) {
      }
    }

Migration Steps

  1. Update initialization:

    // v0.x
    CoBuy.initialize({ apiKey: "pk_..." });
    
    // v1.0
    CoBuy.init({ merchantKey: "pk_..." });
  2. Update theme property names:

    // v0.x
    theme: {
      buttonColor: "#...",
      darkBg: false,
    }
    
    // v1.0
    theme: {
      primaryColor: "#...",
      // Dark mode coming in v2.0
    }
  3. Update error handling:

    • Import error classes: import { CoBuyInvalidConfigError } from "@cobuy/sdk"
    • Check error instanceof ErrorClass instead of error codes

Deprecations

These will be removed in v2.0:

  • CoBuy.getRawApiClient() — Use CoBuy.getApiClient() instead
  • options.onGroupJoined callback — Use onCheckout instead

License

MIT

Support

For issues, feature requests, or questions, please contact [email protected] or create an issue in the repository.


Version: 1.0.0 Last Updated: February 2026