npm package: @one-payments/adapters-web
Description: Web platform adapters for One Payments SDK
Author: One Payments
Latest version: 1.0.6

# @one-payments/adapters-web

Web platform adapters for One Payments SDK. Provides browser-based implementations for HTTP, Storage, Crypto, and Timer operations.

## Features

- 🌐 **Browser APIs** - Built on native Web APIs (Fetch, LocalStorage, Web Crypto, setTimeout)
- 🔒 **Secure** - Uses Web Crypto API for HMAC generation
- 💾 **Persistent Storage** - LocalStorage for session management
- ⚡ **Lightweight** - No external dependencies
- 🎯 **TypeScript** - Full type safety
- 🧩 **Framework Agnostic** - Works with React, Vue, Angular, and vanilla JS

## Installation

```bash
npm install @one-payments/adapters-web
# or
yarn add @one-payments/adapters-web
# or
pnpm add @one-payments/adapters-web
```

## Usage

### Basic Usage

```typescript
import { createWebAdapters } from '@one-payments/adapters-web';

// Create adapters instance
const adapters = createWebAdapters();

// Use with any One Payments component
// React example:
<OnePayment
  config={config}
  adapters={adapters}
  amount={5000}
  currency="USD"
  orderId="order-123"
/>
```

### React

```tsx
import { createWebAdapters } from '@one-payments/adapters-web';
import { OnePayment } from '@one-payments/react';

function CheckoutPage() {
  const adapters = createWebAdapters();

  return (
    <OnePayment
      config={config}
      adapters={adapters}
      {...paymentProps}
    />
  );
}
```

### Vue 3

```vue
<script setup lang="ts">
import { createWebAdapters } from '@one-payments/adapters-web';
import { OnePayment } from '@one-payments/vue';

const adapters = createWebAdapters();
</script>

<template>
  <OnePayment
    :config="config"
    :adapters="adapters"
    v-bind="paymentProps"
  />
</template>
```

### Angular

```typescript
import { Component } from '@angular/core';
import { createWebAdapters } from '@one-payments/adapters-web';
import { OnePaymentComponent } from '@one-payments/angular';

@Component({
  selector: 'app-checkout',
  standalone: true,
  imports: [OnePaymentComponent],
  template: `
    <one-payment
      [config]="config"
      [adapters]="adapters"
    />
  `
})
export class CheckoutComponent {
  adapters = createWebAdapters();
}
```

### Vanilla JavaScript

```html
<!DOCTYPE html>
<html>
<head>
  <script type="module" src="https://unpkg.com/@one-payments/web-components"></script>
</head>
<body>
  <one-payment id="payment"></one-payment>

  <script type="module">
    import { createWebAdapters } from 'https://unpkg.com/@one-payments/adapters-web';

    const adapters = createWebAdapters();
    const paymentElement = document.getElementById('payment');

    paymentElement.adapters = adapters;
    paymentElement.config = { /*...*/ };
  </script>
</body>
</html>
```

## Server-Side Rendering (SSR)

**Important:** This package uses browser-only APIs and cannot be used during server-side rendering. Initialize adapters on the client-side only.

### Next.js Integration

For Next.js (and other SSR frameworks), dynamically import and initialize adapters in `useEffect`:

```tsx
'use client';

import { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
import type { Adapters } from '@one-payments/core';

// Dynamic import of payment component
const OnePayment = dynamic(
  () => import('@one-payments/react').then((mod) => mod.OnePayment),
  { ssr: false }
);

export default function CheckoutPage() {
  const [adapters, setAdapters] = useState<Adapters | null>(null);

  // Initialize adapters on client-side only
  useEffect(() => {
    import('@one-payments/adapters-web').then(({ createWebAdapters }) => {
      setAdapters(createWebAdapters());
    });
  }, []);

  if (!adapters) {
    return <div>Loading payment system...</div>;
  }

  return (
    <OnePayment
      config={config}
      adapters={adapters}
      {...props}
    />
  );
}
```

**Why this is needed:**
- Web APIs like `window`, `localStorage`, `crypto.subtle` don't exist in Node.js
- SSR renders on the server first, causing "ReferenceError: window is not defined"
- Dynamic import with `ssr: false` ensures code only runs in the browser

See the [Next.js Integration Guide](../../docs/integration-guides/nextjs.md) for complete instructions.

### Nuxt 3

```vue
<script setup>
import { ref, onMounted } from 'vue';

const adapters = ref(null);

onMounted(async () => {
  const { createWebAdapters } = await import('@one-payments/adapters-web');
  adapters.value = createWebAdapters();
});
</script>

<template>
  <ClientOnly>
    <OnePayment v-if="adapters" :adapters="adapters" />
  </ClientOnly>
</template>
```

## Adapter Implementations

### HTTP Adapter

Uses the native `fetch` API for HTTP requests:

```typescript
interface HttpAdapter {
  fetch(url: string, options?: RequestInit): Promise<Response>;
}

// Implementation
const httpAdapter = {
  fetch: (url, options) => window.fetch(url, options)
};
```

### Storage Adapter

Uses `localStorage` for persistent client-side storage:

```typescript
interface StorageAdapter {
  getItem(key: string): Promise<string | null>;
  setItem(key: string, value: string): Promise<void>;
  removeItem(key: string): Promise<void>;
}

// Implementation
const storageAdapter = {
  getItem: (key) => Promise.resolve(localStorage.getItem(key)),
  setItem: (key, value) => Promise.resolve(localStorage.setItem(key, value)),
  removeItem: (key) => Promise.resolve(localStorage.removeItem(key))
};
```

**Storage Keys Used:**
- `one-payments-session-*` - Payment session data
- `one-payments-config-*` - Cached configuration

### Crypto Adapter

Uses Web Crypto API for secure cryptographic operations:

```typescript
interface CryptoAdapter {
  generateHMAC(message: string, secret: string): Promise<string>;
  randomUUID(): string;
}

// Implementation uses crypto.subtle for HMAC-SHA256
const cryptoAdapter = {
  generateHMAC: async (message, secret) => {
    const encoder = new TextEncoder();
    const keyData = encoder.encode(secret);
    const messageData = encoder.encode(message);

    const key = await crypto.subtle.importKey(
      'raw',
      keyData,
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign']
    );

    const signature = await crypto.subtle.sign('HMAC', key, messageData);
    return Array.from(new Uint8Array(signature))
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  },
  randomUUID: () => crypto.randomUUID()
};
```

### Timer Adapter

Uses native JavaScript timer functions:

```typescript
interface TimerAdapter {
  setTimeout(callback: () => void, delay: number): number;
  clearTimeout(id: number): void;
}

// Implementation
const timerAdapter = {
  setTimeout: (callback, delay) => window.setTimeout(callback, delay),
  clearTimeout: (id) => window.clearTimeout(id)
};
```

## Browser Support

### Required Browser Features

- **Fetch API** - All modern browsers (IE11 needs polyfill)
- **LocalStorage** - All browsers supporting Web Storage API
- **Web Crypto API** - Chrome 37+, Firefox 34+, Safari 11+, Edge 12+
- **crypto.randomUUID()** - Chrome 92+, Firefox 95+, Safari 15.4+

### Polyfills

For older browsers, you may need polyfills:

```html
<!-- Fetch polyfill for IE11 -->
<script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3.6.2/dist/fetch.umd.js"></script>

<!-- crypto.randomUUID polyfill -->
<script>
if (!crypto.randomUUID) {
  crypto.randomUUID = function() {
    return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
      (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
    );
  };
}
</script>
```

## Browser Compatibility

| Browser | Minimum Version | Notes |
|---------|----------------|-------|
| Chrome | 92+ | Full support |
| Firefox | 95+ | Full support |
| Safari | 15.4+ | Full support |
| Edge | 92+ | Full support |
| Mobile Safari | iOS 15.4+ | Full support |
| Chrome Mobile | 92+ | Full support |

## Security Considerations

### HMAC Generation

- Uses Web Crypto API's `crypto.subtle` for secure HMAC-SHA256
- Secret keys never leave the browser
- HMAC signatures used for API request authentication

### Storage Security

- Uses `localStorage` which is origin-isolated
- Data is accessible only to your domain
- No sensitive card data is stored (PCI compliance)
- Session tokens expire after use

### Content Security Policy (CSP)

If your site uses CSP, ensure these directives:

```http
Content-Security-Policy:
  script-src 'self' 'unsafe-eval';
  connect-src 'self' https://*.one.ooo;
  frame-src https://*.one.ooo;
```

## Comparison with Other Adapters

### vs. @one-payments/adapters-crypto-js

| Feature | adapters-web | adapters-crypto-js |
|---------|--------------|-------------------|
| Environment | Modern browsers | Legacy browsers, WebView |
| Crypto | Web Crypto API | CryptoJS library |
| Bundle Size | ~2KB | ~50KB |
| Performance | Native (faster) | JavaScript (slower) |
| Use Case | Standard web apps | React Native WebView, IE11 |

**Use `@one-payments/adapters-crypto-js` when:**
- Supporting IE11 or older browsers
- Using React Native WebView (no Web Crypto API)
- Server-side rendering with crypto operations

**Use `@one-payments/adapters-web` when:**
- Building modern web applications
- Supporting Chrome, Firefox, Safari, Edge (latest versions)
- Performance is a priority

### vs. @one-payments/adapters-native

| Feature | adapters-web | adapters-native |
|---------|--------------|-----------------|
| Platform | Web browsers | React Native |
| Storage | localStorage | AsyncStorage |
| HTTP | fetch | React Native fetch |
| Use Case | Web apps | Native mobile apps |

## Troubleshooting

### Error: "window is not defined"

**Cause:** Trying to import adapters during SSR

**Solution:** Initialize in `useEffect` (React) or `onMounted` (Vue):

```tsx
// ❌ Don't do this
const adapters = createWebAdapters();

// ✅ Do this
const [adapters, setAdapters] = useState(null);
useEffect(() => {
  import('@one-payments/adapters-web').then(({ createWebAdapters }) => {
    setAdapters(createWebAdapters());
  });
}, []);
```

### Error: "crypto.subtle is undefined"

**Cause:** Page not served over HTTPS (Web Crypto requires secure context)

**Solution:**
- Use HTTPS in production
- For localhost, modern browsers allow HTTP
- For testing, use `chrome://flags/#unsafely-treat-insecure-origin-as-secure`

### Error: "localStorage is not defined"

**Cause:** Browser privacy mode or storage disabled

**Solution:** Check if storage is available:

```typescript
try {
  const adapters = createWebAdapters();
} catch (error) {
  console.error('Storage not available:', error);
  // Show message to user to disable privacy mode
}
```

## Testing

Mock adapters in tests:

```typescript
// test-utils.ts
export const mockAdapters = {
  http: {
    fetch: jest.fn()
  },
  storage: {
    getItem: jest.fn(),
    setItem: jest.fn(),
    removeItem: jest.fn()
  },
  crypto: {
    generateHMAC: jest.fn().mockResolvedValue('mock-hmac'),
    randomUUID: jest.fn().mockReturnValue('mock-uuid')
  },
  timer: {
    setTimeout: jest.fn(),
    clearTimeout: jest.fn()
  }
};

// Your test
it('should process payment', () => {
  render(<OnePayment adapters={mockAdapters} {...props} />);
  // ...
});
```

## Related Packages

- [@one-payments/core](../core) - Core types and interfaces
- [@one-payments/react](../react) - React wrapper
- [@one-payments/vue](../vue) - Vue wrapper
- [@one-payments/angular](../angular) - Angular wrapper
- [@one-payments/adapters-crypto-js](../adapters-crypto-js) - CryptoJS-based adapter
- [@one-payments/adapters-native](../adapters-native) - React Native adapter

## Requirements

- Modern browser with ES2020 support
- HTTPS (for Web Crypto API)
- LocalStorage enabled

## Bundle Size

- **Minified:** ~2KB
- **Gzipped:** ~1KB

## License

MIT