> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shipnative.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Extending Services

> Add third-party services like OneSignal, Firebase, or Convex

Shipnative's service architecture is designed to be modular. Adding a new service follows a consistent pattern across the codebase.

## Architecture Overview

Services implement platform-agnostic interfaces, making it easy to swap providers or add new ones:

```
apps/app/app/
├── types/              # Service interfaces
├── services/           # Service implementations
├── config/
│   ├── env.ts          # Environment variables (Zod validated)
│   └── features.ts     # Feature flags
└── app.tsx             # Service initialization
```

**Key principles:**

* Each service implements a TypeScript interface
* Platform detection handles iOS/Android/Web differences automatically
* Services fail gracefully when credentials are missing
* Feature flags control service availability

***

## Adding a New Service

Let's walk through adding a hypothetical service. The same pattern applies whether you're adding OneSignal, Firebase, Convex, Amplitude, or any other provider.

### Step 1: Define the Interface

Create a type definition in `apps/app/app/types/`:

```typescript theme={null}
// apps/app/app/types/newservice.ts
export type NewServicePlatform = "newservice"

export interface NewServiceConfig {
  apiKey: string
  // Add platform-specific options as needed
}

export interface NewServiceInterface {
  platform: NewServicePlatform
  initialize(config: NewServiceConfig): Promise<void>

  // Define your service methods
  doSomething(data: unknown): Promise<void>

  // Optional cleanup
  destroy?(): Promise<void>
}
```

Export from the types index:

```typescript theme={null}
// apps/app/app/types/index.ts
export * from "./newservice"
```

### Step 2: Add Environment Variables

Update the Zod schema in `apps/app/app/config/env.ts`:

```typescript theme={null}
const EnvSchema = z.object({
  // ... existing variables

  // New service
  newserviceApiKey: z.string().optional(),
})
```

Add to the `isServiceConfigured` function:

```typescript theme={null}
export function isServiceConfigured(
  service: "supabase" | "revenuecat" | "posthog" | "sentry" | "newservice"
): boolean {
  switch (service) {
    // ... existing cases
    case "newservice":
      return !!env.newserviceApiKey
  }
}
```

### Step 3: Add Feature Flag

Update `apps/app/app/config/features.ts`:

```typescript theme={null}
export interface FeatureFlags {
  // ... existing flags
  enableNewService: boolean
}

function getFeatureFlags(): FeatureFlags {
  return {
    // ... existing flags
    enableNewService: isServiceConfigured("newservice"),
  }
}
```

### Step 4: Create the Service

Create the service implementation in `apps/app/app/services/`:

```typescript theme={null}
// apps/app/app/services/newservice.ts
import { Platform } from "react-native"
import { env } from "../config/env"
import type { NewServiceInterface, NewServiceConfig } from "../types/newservice"
import { logger } from "../utils/Logger"

// Platform-specific SDK loading
let SDK: typeof import("newservice-sdk") | null = null

if (Platform.OS !== "web") {
  try {
    SDK = require("newservice-react-native")
  } catch {
    if (__DEV__) logger.warn("NewService SDK not available")
  }
} else {
  // Web SDK loaded differently if needed
}

class NewService implements NewServiceInterface {
  platform = "newservice" as const
  private initialized = false

  async initialize(config: NewServiceConfig): Promise<void> {
    if (this.initialized) return

    try {
      SDK?.configure(config.apiKey)
      this.initialized = true
      logger.info("NewService initialized")
    } catch (error) {
      logger.error("NewService init failed", {}, error as Error)
    }
  }

  async doSomething(data: unknown): Promise<void> {
    if (!this.initialized) {
      logger.warn("NewService not initialized")
      return
    }

    SDK?.doSomething(data)
  }

  async destroy(): Promise<void> {
    // Cleanup if needed
  }
}

// Export singleton
export const newservice: NewServiceInterface = new NewService()

// Export init function
export function initNewService(): void {
  if (!env.newserviceApiKey) {
    if (__DEV__) {
      logger.info("NewService not configured - skipping")
    }
    return
  }

  newservice.initialize({
    apiKey: env.newserviceApiKey,
  })
}
```

### Step 5: Initialize in App

Add initialization to `apps/app/app/app.tsx`:

```typescript theme={null}
import { initNewService } from "./services/newservice"

// Inside useEffect with other service inits
useEffect(() => {
  InteractionManager.runAfterInteractions(() => {
    // ... existing inits
    initNewService()
  })
}, [])
```

### Step 6: Use the Service

```typescript theme={null}
import { newservice } from "@/services/newservice"
import { features } from "@/config/features"

function MyComponent() {
  useEffect(() => {
    if (features.enableNewService) {
      newservice.doSomething({ key: "value" })
    }
  }, [])
}
```

***

## Quick Reference: Files to Update

| File                     | What to Add                        |
| ------------------------ | ---------------------------------- |
| `types/newservice.ts`    | Interface definition               |
| `types/index.ts`         | Export the new types               |
| `config/env.ts`          | Zod schema + `isServiceConfigured` |
| `config/features.ts`     | Feature flag                       |
| `services/newservice.ts` | Service implementation             |
| `app.tsx`                | Initialization call                |

***

## Common Integrations

### Push Notifications (OneSignal)

Replace or extend the existing Expo notifications:

```typescript theme={null}
// types/pushNotifications.ts
export interface PushService {
  initialize(): Promise<void>
  requestPermission(): Promise<boolean>
  subscribe(userId: string): Promise<void>
  unsubscribe(): Promise<void>
}
```

### Realtime Database (Convex)

Add alongside or instead of Supabase realtime:

```typescript theme={null}
// types/realtime.ts
export interface RealtimeService {
  connect(): Promise<void>
  subscribe<T>(query: string, callback: (data: T) => void): () => void
  mutate(mutation: string, args: unknown): Promise<void>
}
```

### Analytics (Amplitude, Mixpanel)

The existing `AnalyticsService` interface works for most providers:

```typescript theme={null}
// Just implement the existing interface
import type { AnalyticsService } from "../types/analytics"

class AmplitudeService implements AnalyticsService {
  platform = "amplitude" as const
  // ... implement methods
}
```

***

## Replacing Existing Services

To swap out a service (e.g., PostHog → Amplitude):

1. Create new implementation using the **same interface**
2. Update the export in the service file
3. No other code changes needed

```typescript theme={null}
// services/analytics.ts
import { AmplitudeService } from "./amplitude"
import { PostHogService } from "./posthog"

// Swap by changing this line
export const analytics: AnalyticsService = new AmplitudeService()
// export const analytics: AnalyticsService = new PostHogService()
```

***

## Platform-Specific SDKs

Many services have different SDKs for mobile and web:

```typescript theme={null}
let MobileSDK: typeof import("service-react-native") | null = null
let WebSDK: typeof import("service-js") | null = null

if (Platform.OS === "web") {
  import("service-js").then((module) => {
    WebSDK = module
  })
} else {
  try {
    MobileSDK = require("service-react-native")
  } catch {
    // SDK not installed
  }
}

// Then use the appropriate one
const sdk = Platform.OS === "web" ? WebSDK : MobileSDK
```

***

## Testing Your Integration

1. **Without credentials:** Service should skip initialization gracefully
2. **With credentials:** Verify initialization logs appear
3. **Cross-platform:** Test on iOS, Android, and Web

Check the console for initialization logs:

```
[NewService] initialized
// or
[NewService] not configured - skipping
```

***

## Troubleshooting

**Service not initializing?**

* Verify environment variable is set in `apps/app/.env`
* Check variable is prefixed with `EXPO_PUBLIC_`
* Restart Metro: `yarn app:start --clear`

**TypeScript errors?**

* Ensure interface is exported from `types/index.ts`
* Check import paths use `@/` alias

**Platform-specific issues?**

* Verify SDK is installed: `yarn add service-react-native`
* Check SDK supports your React Native version
* Review SDK documentation for Expo compatibility
