Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Docs/1_Authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ const userId = "your_user_id";
await Courier.shared.signIn({ userId: userId, accessToken: jwt });
```

For EU-hosted workspaces, pass the built-in EU endpoint preset:

```javascript
import Courier, { getCourierApiUrlsForRegion } from "@trycourier/courier-react-native";

await Courier.shared.signIn({
userId: userId,
accessToken: jwt,
apiUrls: getCourierApiUrlsForRegion("eu")
});
```

If the token is expired, you can generate a new one from your endpoint and call `Courier.shared.signIn(...)` again. You will need to check the token manually for expiration or generate a new one when the user views a specific screen in your app. It is up to you to handle token expiration and refresh based on your security needs.

## 4. Sign your user out
Expand Down
1 change: 1 addition & 0 deletions Docs/5_Client.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const client = new CourierClient({
clientKey: "...", // Optional. Used only for Inbox
tenantId: .., // Optional. Used for scoping a client to a specific tenant
connectionId: "...", // Optional. Used for inbox websocket
apiUrls: getCourierApiUrlsForRegion("eu"), // Optional. Use for EU-hosted workspaces
});

// Details about the client
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,17 @@ Starter projects using this SDK.
We want to make this the best SDK for managing notifications! Have an idea or feedback about our SDKs? Let us know!

[Courier React Native Issues](https://github.com/trycourier/courier-react-native/issues)

## EU endpoints

If your workspace uses EU-hosted Courier endpoints, pass the built-in EU preset through `apiUrls`.

```tsx
import Courier, { getCourierApiUrlsForRegion } from "@trycourier/courier-react-native";

await Courier.shared.signIn({
userId: "your_user_id",
accessToken: jwt,
apiUrls: getCourierApiUrlsForRegion("eu")
});
```
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class CourierClientModule(
userId = userId,
connectionId = options.getString("connectionId"),
tenantId = options.getString("tenantId"),
apiUrls = options.getMap("apiUrls")?.toApiUrls() ?: CourierClient.ApiUrls(),
showLogs = showLogs
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.courierreactnative

import com.courier.android.Courier
import com.courier.android.client.CourierClient
import com.courier.android.models.CourierAuthenticationListener
import com.courier.android.models.CourierInboxListener
import com.courier.android.models.InboxMessageSet
Expand Down Expand Up @@ -92,6 +93,7 @@ class CourierSharedModule(
clientKey: String?,
userId: String,
tenantId: String?,
apiUrls: com.facebook.react.bridge.ReadableMap?,
showLogs: Boolean,
promise: Promise
) {
Expand All @@ -101,6 +103,7 @@ class CourierSharedModule(
tenantId = tenantId,
accessToken = accessToken,
clientKey = clientKey,
apiUrls = apiUrls?.toApiUrls() ?: CourierClient.ApiUrls(),
showLogs = showLogs
)
promise.resolve(null)
Expand Down
10 changes: 10 additions & 0 deletions android/src/main/java/com/courierreactnative/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ internal fun ReadableMap.toInfoViewStyle(context: Context): CourierStyles.InfoVi

}

internal fun ReadableMap.toApiUrls(): com.courier.android.client.CourierClient.ApiUrls {
val defaults = com.courier.android.client.CourierClient.ApiUrls()
return com.courier.android.client.CourierClient.ApiUrls(
rest = getString("rest") ?: defaults.rest,
graphql = getString("graphql") ?: defaults.graphql,
inboxGraphql = getString("inboxGraphql") ?: defaults.inboxGraphql,
inboxWebSocket = getString("inboxWebSocket") ?: defaults.inboxWebSocket
)
}

internal fun Map<String, Any>?.toWritableMap(): WritableMap {
val map = Arguments.createMap()
this?.forEach { (key, value) ->
Expand Down
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
presets: ['module:@react-native/babel-preset'],
};
1 change: 1 addition & 0 deletions ios/CourierClientModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal class CourierClientModule: CourierReactNativeEventEmitter {
userId: userId,
connectionId: options["connectionId"] as? String,
tenantId: options["tenantId"] as? String,
baseUrls: (options["apiUrls"] as? NSDictionary)?.toCourierApiUrls() ?? CourierClient.ApiUrls(),
showLogs: showLogs
)

Expand Down
1 change: 1 addition & 0 deletions ios/CourierReactNativeModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ @interface RCT_EXTERN_MODULE(CourierSharedModule, NSObject)
withClientKey: (NSString*)clientKey
withUserId: (NSString*)userId
withTenantId: (NSString*)tenantId
withApiUrls: (NSDictionary*)apiUrls
withShowLogs: (BOOL*)showLogs
withResolver: (RCTPromiseResolveBlock)resolve
withRejecter: (RCTPromiseRejectBlock)reject
Expand Down
13 changes: 10 additions & 3 deletions ios/CourierSharedModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ class CourierSharedModule: CourierReactNativeEventEmitter {
"userId": options.userId as Any,
"connectionId": options.connectionId as Any,
"tenantId": options.tenantId as Any,
"showLogs": options.showLogs as Any
"showLogs": options.showLogs as Any,
"apiUrls": [
"rest": options.apiUrls.rest,
"graphql": options.apiUrls.graphql,
"inboxGraphql": options.apiUrls.inboxGraphql,
"inboxWebSocket": options.apiUrls.inboxWebSocket
] as Any
]
.compactMapValues { $0 }

Expand Down Expand Up @@ -90,8 +96,8 @@ class CourierSharedModule: CourierReactNativeEventEmitter {
}
}

@objc(signIn:withClientKey:withUserId:withTenantId:withShowLogs:withResolver:withRejecter:)
func signIn(accessToken: String, clientKey: String?, userId: String, tenantId: String?, showLogs: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
@objc(signIn:withClientKey:withUserId:withTenantId:withApiUrls:withShowLogs:withResolver:withRejecter:)
func signIn(accessToken: String, clientKey: String?, userId: String, tenantId: String?, apiUrls: NSDictionary?, showLogs: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {

Task {

Expand All @@ -100,6 +106,7 @@ class CourierSharedModule: CourierReactNativeEventEmitter {
tenantId: tenantId,
accessToken: accessToken,
clientKey: clientKey,
baseUrls: apiUrls?.toCourierApiUrls() ?? CourierClient.ApiUrls(),
showLogs: showLogs
)

Expand Down
10 changes: 10 additions & 0 deletions ios/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,16 @@ internal extension NSDictionary {
)

}

func toCourierApiUrls() -> CourierClient.ApiUrls {
let defaults = CourierClient.ApiUrls()
return CourierClient.ApiUrls(
rest: self["rest"] as? String ?? defaults.rest,
graphql: self["graphql"] as? String ?? defaults.graphql,
inboxGraphql: self["inboxGraphql"] as? String ?? defaults.inboxGraphql,
inboxWebSocket: self["inboxWebSocket"] as? String ?? defaults.inboxWebSocket
)
}

}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
"quoteProps": "consistent",
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "off",
"trailingComma": "none",
"useTabs": false
}
]
Expand Down
30 changes: 30 additions & 0 deletions src/CourierApiUrls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export interface CourierApiUrls {
rest: string;
graphql: string;
inboxGraphql: string;
inboxWebSocket: string;
}

export type CourierApiRegion = 'us' | 'eu';

export const DEFAULT_COURIER_API_URLS: CourierApiUrls = {
rest: 'https://api.courier.com',
graphql: 'https://api.courier.com/client/q',
inboxGraphql: 'https://inbox.courier.io/q',
inboxWebSocket: 'wss://realtime.courier.io'
};

export const EU_COURIER_API_URLS: CourierApiUrls = {
rest: 'https://api.eu.courier.com',
graphql: 'https://api.eu.courier.com/client/q',
inboxGraphql: 'https://inbox.eu.courier.io/q',
inboxWebSocket: 'wss://realtime.eu.courier.io'
};

export function getCourierApiUrlsForRegion(
region: CourierApiRegion
): CourierApiUrls {
return region === 'eu'
? { ...EU_COURIER_API_URLS }
: { ...DEFAULT_COURIER_API_URLS };
}
76 changes: 76 additions & 0 deletions src/__tests__/api-urls.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
jest.mock('../Modules', () => ({
Modules: {
getNativeComponent: jest.fn(() => 'mock-native-component'),
Client: {
addClient: jest.fn(() => 'client-id'),
removeClient: jest.fn(() => 'client-id')
},
Shared: {
attachEmitter: jest.fn(() => Promise.resolve('emitter-id')),
signIn: jest.fn(() => Promise.resolve())
},
System: {
setIOSForegroundPresentationOptions: jest.fn(() => 'ok')
}
}
}));

jest.mock('../Broadcaster', () =>
jest.fn().mockImplementation(() => ({
addListener: jest.fn(() => Promise.resolve(undefined))
}))
);

import Courier, {
CourierClient,
DEFAULT_COURIER_API_URLS,
EU_COURIER_API_URLS,
getCourierApiUrlsForRegion
} from '../index';
import { Modules } from '../Modules';

describe('CourierApiUrls', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('returns a cloned EU preset', () => {
const apiUrls = getCourierApiUrlsForRegion('eu');

expect(apiUrls).toEqual(EU_COURIER_API_URLS);
expect(apiUrls).not.toBe(EU_COURIER_API_URLS);
});

it('passes apiUrls when creating a client', () => {
const client = new CourierClient({
userId: 'user-123',
showLogs: true,
apiUrls: EU_COURIER_API_URLS
});

expect(client.options.apiUrls).toEqual(EU_COURIER_API_URLS);
expect(Modules.Client.addClient).toHaveBeenCalledWith(
expect.objectContaining({
userId: 'user-123',
apiUrls: EU_COURIER_API_URLS
})
);
});

it('passes apiUrls during shared sign in', async () => {
await Courier.shared.signIn({
accessToken: 'jwt',
userId: 'user-123',
apiUrls: DEFAULT_COURIER_API_URLS
});

expect(Modules.Shared.signIn).toHaveBeenCalledWith(
'jwt',
undefined,
'user-123',
undefined,
DEFAULT_COURIER_API_URLS,
expect.any(Boolean)
);
});
});
6 changes: 5 additions & 1 deletion src/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
it.todo('write a test');
describe('placeholder', () => {
it('keeps the test suite active', () => {
expect(true).toBe(true);
});
});
33 changes: 20 additions & 13 deletions src/client/CourierClient.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { BrandClient } from "..";
import { ClientModule } from "./ClientModule";
import { InboxClient } from "./InboxClient";
import { PreferenceClient } from "./PreferenceClient";
import { TokenClient } from "./TokenClient";
import { TrackingClient } from "./TrackingClient";
import { BrandClient } from '..';
import { ClientModule } from './ClientModule';
import { InboxClient } from './InboxClient';
import { PreferenceClient } from './PreferenceClient';
import { TokenClient } from './TokenClient';
import { TrackingClient } from './TrackingClient';
import { CourierApiUrls } from '../CourierApiUrls';

export interface CourierClientOptions {
userId: string;
Expand All @@ -12,38 +13,44 @@ export interface CourierClientOptions {
clientKey?: string;
connectionId?: string;
tenantId?: string;
apiUrls?: CourierApiUrls;
}

export class CourierClient extends ClientModule {

public readonly options: CourierClientOptions;
public readonly tokens: TokenClient;
public readonly brands: BrandClient;
public readonly inbox: InboxClient;
public readonly preferences: PreferenceClient;
public readonly tracking: TrackingClient;

constructor(props: { userId: string, jwt?: string, clientKey?: string, connectionId?: string, tenantId?: string, showLogs?: boolean }) {

constructor(props: {
userId: string;
jwt?: string;
clientKey?: string;
connectionId?: string;
tenantId?: string;
apiUrls?: CourierApiUrls;
showLogs?: boolean;
}) {
const options = {
userId: props.userId,
showLogs: props.showLogs ?? __DEV__,
jwt: props.jwt,
clientKey: props.clientKey,
connectionId: props.clientKey,
tenantId: props.tenantId,
apiUrls: props.apiUrls ? { ...props.apiUrls } : undefined
};

super(options);

this.options = options
this.options = options;

this.tokens = new TokenClient(this.clientId);
this.brands = new BrandClient(this.clientId);
this.inbox = new InboxClient(this.clientId);
this.preferences = new PreferenceClient(this.clientId);
this.tracking = new TrackingClient(this.clientId);

}

}
}
Loading
Loading