npm package: @mwalek/expo-wp-notifications
Description: Client SDK for Expo WP Notifications WordPress plugin
Author: mwalek
Latest version: 1.0.0

# Expo WP Notifications Client SDK

A lightweight TypeScript client for integrating React Native / Expo apps with the [Expo WP Notifications](https://github.com/mwalek/expo-wp-notifications) WordPress plugin.

## Features

- Zero runtime dependencies
- Full TypeScript support
- Automatic heartbeat scheduling
- Persistent registration state
- Configurable retry logic
- Works with Expo and bare React Native

## Installation

```bash
npm install @mwalek/expo-wp-notifications
# or
yarn add @mwalek/expo-wp-notifications
```

## Quick Start

```typescript
import { ExpoWPNotifications, createAsyncStorageAdapter } from '@mwalek/expo-wp-notifications';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';

// Initialize client
const ewpn = new ExpoWPNotifications({
  baseUrl: 'https://yoursite.com',
  storage: createAsyncStorageAdapter(AsyncStorage),
  autoHeartbeat: true, // Send heartbeat every 24h
  debug: __DEV__,
});

// Register for push notifications
async function registerForPushNotifications(userId?: number) {
  // Get Expo push token
  const { data: pushToken } = await Notifications.getExpoPushTokenAsync();

  // Register with WordPress
  const result = await ewpn.register({
    pushToken,
    deviceId: Device.deviceId ?? 'unknown',
    platform: Device.osName?.toLowerCase() as 'ios' | 'android',
    userId,
  });

  if (result.success) {
    console.log('Registered successfully:', result.data.token_id);
  } else {
    console.error('Registration failed:', result.error.message);
  }
}

// Logout (unregister token)
async function logout() {
  await ewpn.unregister();
}
```

## API Reference

### `new ExpoWPNotifications(config)`

Create a new client instance.

```typescript
const ewpn = new ExpoWPNotifications({
  // Required
  baseUrl: 'https://yoursite.com',

  // Optional
  namespace: 'ewpn/v1',           // REST API namespace
  autoHeartbeat: false,            // Enable automatic heartbeat
  heartbeatInterval: 86400000,     // Heartbeat interval (24h default)
  storage: new MemoryStorage(),    // Storage adapter
  timeout: 30000,                  // Request timeout (30s)
  retries: 3,                      // Retry attempts
  headers: {},                     // Custom headers
  debug: false,                    // Enable debug logging
});
```

### `ewpn.register(data)`

Register a push token with WordPress.

```typescript
const result = await ewpn.register({
  pushToken: 'ExponentPushToken[xxx]',
  deviceId: 'unique-device-id',
  platform: 'ios', // 'ios' | 'android' | 'web'
  userId: 123,     // Optional: WordPress user ID
  appId: 'com.example.app', // Optional
  metadata: {},    // Optional: custom data
});

if (result.success) {
  console.log('Token ID:', result.data.token_id);
} else {
  console.error('Error:', result.error.message);
}
```

### `ewpn.heartbeat()`

Send a heartbeat to confirm the token is still active.

```typescript
const result = await ewpn.heartbeat();
```

### `ewpn.unregister()`

Unregister the current device token.

```typescript
const result = await ewpn.unregister();
```

### `ewpn.isRegistered()`

Check if the device is registered.

```typescript
const registered = await ewpn.isRegistered();
```

### `ewpn.getRegistrationState()`

Get the current registration state.

```typescript
const state = await ewpn.getRegistrationState();
// { deviceId, pushToken, tokenId, userId, registeredAt }
```

### `ewpn.updateUserId(userId)`

Update the user ID for the current registration (e.g., after login).

```typescript
await ewpn.updateUserId(123);
```

### `ewpn.startAutoHeartbeat()` / `ewpn.stopAutoHeartbeat()`

Manually control automatic heartbeat.

```typescript
ewpn.startAutoHeartbeat();
ewpn.stopAutoHeartbeat();
```

### `ewpn.destroy()`

Cleanup resources (stops heartbeat timer).

```typescript
ewpn.destroy();
```

## Storage Adapters

### AsyncStorage (React Native)

```typescript
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createAsyncStorageAdapter } from '@mwalek/expo-wp-notifications';

const ewpn = new ExpoWPNotifications({
  baseUrl: 'https://yoursite.com',
  storage: createAsyncStorageAdapter(AsyncStorage),
});
```

### Custom Storage

```typescript
import type { StorageAdapter } from '@mwalek/expo-wp-notifications';

const customStorage: StorageAdapter = {
  getItem: async (key) => { /* ... */ },
  setItem: async (key, value) => { /* ... */ },
  removeItem: async (key) => { /* ... */ },
};
```

## Complete Example

```typescript
import { useEffect, useState } from 'react';
import { ExpoWPNotifications, createAsyncStorageAdapter } from '@mwalek/expo-wp-notifications';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';

// Create singleton instance
export const ewpn = new ExpoWPNotifications({
  baseUrl: 'https://twala.shop',
  storage: createAsyncStorageAdapter(AsyncStorage),
  autoHeartbeat: true,
  debug: __DEV__,
});

// Hook for notification setup
export function useNotifications(userId?: number) {
  const [isRegistered, setIsRegistered] = useState(false);

  useEffect(() => {
    async function setup() {
      // Request permissions
      const { status } = await Notifications.requestPermissionsAsync();
      if (status !== 'granted') return;

      // Get push token
      const { data: pushToken } = await Notifications.getExpoPushTokenAsync();

      // Register with WordPress
      const result = await ewpn.register({
        pushToken,
        deviceId: Device.deviceId ?? `device-${Date.now()}`,
        platform: Device.osName?.toLowerCase() as 'ios' | 'android',
        userId,
      });

      setIsRegistered(result.success);
    }

    setup();

    return () => {
      ewpn.destroy();
    };
  }, [userId]);

  return { isRegistered };
}

// Logout function
export async function logout() {
  await ewpn.unregister();
  // ... other logout logic
}
```

## Error Handling

All methods return a `Result` type:

```typescript
type Result<T> =
  | { success: true; data: T }
  | { success: false; error: { code: string; message: string } };
```

Common error codes:
- `not_registered` - No token registered (call register first)
- `invalid_token` - Push token format is invalid
- `network_error` - Network request failed
- `timeout` - Request timed out
- `api_error` - Server returned an error

## License

MIT