Skip to content
Merged
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
32 changes: 14 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
![Platform React Native SDK](./assets/github-reactNative-sdk-banner.png)

# YouVersion React Native SDK

[![npm version](https://badge.fury.io/js/@youversion%2Freact-native-sdk.svg)](https://www.npmjs.com/package/@youversion/platform-sdk-reactnative)
[![CI Status](https://github.com/youversion/platform-sdk-reactnative/workflows/CI/badge.svg)](https://github.com/youversion/platform-sdk-reactnative/actions)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
Expand All @@ -15,7 +16,6 @@ A lightweight React Native SDK for integrating YouVersion Platform features into
- [Release Process](RELEASING.md)
- [Changelog](CHANGELOG.md)


> [!important]
> The React Native SDK is currently **iOS-only**. Android support is under active development and not yet available for use.

Expand Down Expand Up @@ -272,17 +272,8 @@ See `BibleReference` properties under the `BibleTextView` component section abov

Presents the YouVersion login flow to the user and resolves with the login result on completion.

```tsx
import { YouVersionAPI } from '@youversion/platform-sdk-reactnative';

const result = await YouVersionAPI.Users.signIn(['bibles', 'highlights']);
```

**Parameters:**
An object with the following optional properties:

- `requiredPermissions?: SignInWithYouVersionPermission[]` - Array of permissions that must be granted by the user for successful login.
- `optionalPermissions?: SignInWithYouVersionPermission[]` - Array of permissions that are requested but not required for login.
An array of permissions you're requesting from the user when they sign in.

Enum values for `SignInWithYouVersionPermission`:

Expand All @@ -293,13 +284,18 @@ Enum values for `SignInWithYouVersionPermission`:
- `bibleActivity`

**Returns:**
`Promise<YouVersionLoginResult>` - An object containing the following details:

| Property | Type | Description |
| ------------- | ---------------------------------- | -------------------------------------------- |
| `accessToken` | `string` | The access token for the authenticated user. |
| `permissions` | `SignInWithYouVersionPermission[]` | The permissions granted by the user. |
| `yvpUserId` | `string` | The YouVersion Platform user ID. |
`Promise<SignInWithYouVersionResult>` - An object containing the following details:

| Property | Type | Description |
| ---------------- | ---------------------------------- | -------------------------------------------- |
| `accessToken` | `string` | Access token for the authenticated user. |
| `permissions` | `SignInWithYouVersionPermission[]` | Permissions granted by the user. |
| `yvpUserId` | `string` | YouVersion Platform user ID. |
| `expiryDate` | `string` | Expiration date of the access token. |
| `refreshToken` | `string` | Refresh token for renewing the access token. |
| `name` | `string` | Name of the authenticated user. |
| `profilePicture` | `string` | URL to the user's profile picture. |
| `email` | `string` | Email address of the authenticated user. |

#### `signOut`

Expand Down
16 changes: 10 additions & 6 deletions example/src/screens/ProfileScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,20 @@ export function ProfileScreen() {

async function handleSignIn() {
try {
await YouVersionAPI.Users.signIn({
requiredPermissions: ["bibles"],
optionalPermissions: ["highlights"],
});
const signInResult = await YouVersionAPI.Users.signIn(["bibles"]);
console.log("Sign-in result:", signInResult);
} catch (error) {
Alert.alert("Error signing in");
console.error("Error signing in:", error);
return;
}

try {
const userInfo = await YouVersionAPI.Users.userInfo();
setCurrentUser(userInfo);
} catch (error) {
Alert.alert("Error signing in");
console.error("Error signing in:", error);
Alert.alert("Error getting user info after sign-in");
console.error("Error getting user info:", error);
}
}

Expand Down
6 changes: 2 additions & 4 deletions ios/RNYouVersionPlatformModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ public class RNYouVersionPlatformModule: Module {
}

AsyncFunction("signIn") { (
requiredPermissions: [String],
optionalPermissions: [String],
permissions: [String],
promise: Promise
) in
YVPAuthAPI.signIn(
requiredPermissions: requiredPermissions,
optionalPermissions: optionalPermissions,
permissions: permissions,
promise: promise
)
}
Expand Down
42 changes: 22 additions & 20 deletions ios/YVPAuthAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,27 @@ import YouVersionPlatform
import AuthenticationServices

struct YVPAuthAPI {
static func signIn(requiredPermissions: [String], optionalPermissions: [String], promise: Promise) {
let required: Set<SignInWithYouVersionPermission> = Set(
requiredPermissions.compactMap(SignInWithYouVersionPermission.init(rawValue:))
)
let optional: Set<SignInWithYouVersionPermission> = Set(
optionalPermissions.compactMap(SignInWithYouVersionPermission.init(rawValue:))
)
static func signIn(permissions: [String], promise: Promise) {
let permissionsSet = Set<SignInWithYouVersionPermission>(
permissions.compactMap(SignInWithYouVersionPermission.init(rawValue:))
)

Task {
do {
let response = try await YouVersionAPI.Users.signIn(
requiredPermissions: required,
optionalPermissions: optional,
contextProvider: ContextProvider()
permissions: permissionsSet,
contextProvider: ContextProvider()
)

if let msg = response.errorMsg, !msg.isEmpty {
promise.reject(YVPError.signInError(message: msg))
return
}

promise.resolve([
"accessToken": response.accessToken,
"permissions": response.permissions.map(\.rawValue),
"yvpUserId": response.yvpUserId
"yvpUserId": response.yvpUserId,
"expiryDate": formatExpiryDate(response.expiryDate),
"refreshToken": response.refreshToken,
"name": response.name,
"profilePicture": response.profilePicture,
"email": response.email
])
} catch {
promise.reject(error)
Expand Down Expand Up @@ -59,6 +55,16 @@ struct YVPAuthAPI {
}
}

private let isoFormatter: ISO8601DateFormatter = {
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return f
}()

func formatExpiryDate(_ expiryDate: Date?) -> String? {
expiryDate.map { isoFormatter.string(from: $0) }
}


class ContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
Expand All @@ -69,7 +75,3 @@ class ContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding
return window
}
}

enum YVPError : Error {
case signInError(message: String)
}
4 changes: 2 additions & 2 deletions mocks/RNYouVersionPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
HighlightResponse,
LanguageOverview,
SignInWithYouVersionPermission,
YouVersionLoginResult,
SignInWithYouVersionResult,
YouVersionUserInfo,
YouVersionVerseOfTheDay,
} from "../src";
Expand All @@ -18,7 +18,7 @@ export function getAccessToken(): string | null {
export function signIn(
requiredPermissions: SignInWithYouVersionPermission[] = [],
optionalPermissions: SignInWithYouVersionPermission[] = [],
): Promise<YouVersionLoginResult> {
): Promise<SignInWithYouVersionResult> {
return Promise.resolve({
accessToken: "mock-access-token",
permissions: [...requiredPermissions, ...optionalPermissions],
Expand Down
24 changes: 10 additions & 14 deletions src/__tests__/RNYouVersionPlatform/native-test.native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,18 @@ describe("YouVersionPlatform", () => {

describe("YouVersionAPI.Users", () => {
describe("#signIn", () => {
it("passes the required and optional permissions", async () => {
const result = await YouVersionAPI.Users.signIn({
requiredPermissions: ["bible_activity"],
optionalPermissions: ["demographics"],
});
it("passes the provided permissions", async () => {
const result = await YouVersionAPI.Users.signIn(["bible_activity"]);

expect(Module.signIn).toHaveBeenCalledWith(
["bible_activity"],
["demographics"],
);
expect(Module.signIn).toHaveBeenCalledWith(["bible_activity"]);

expect(result).toEqual({
accessToken: "mock-access-token",
permissions: ["bible_activity", "demographics"],
yvpUserId: "mock-yvp-user-id",
});
expect(result).toEqual(
expect.objectContaining({
accessToken: "mock-access-token",
permissions: ["bible_activity"],
yvpUserId: "mock-yvp-user-id",
}),
);
});
});

Expand Down
26 changes: 7 additions & 19 deletions src/api/users.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,21 @@
import { Module } from "../native";
import {
SignInWithYouVersionPermission,
YouVersionLoginResult,
SignInWithYouVersionResult,
YouVersionUserInfo,
} from "../types";

type SignInOptions = {
/** Array of permissions that are required for a successful login */
requiredPermissions?: SignInWithYouVersionPermission[];

/** Array of permissions that are optional for the user to select */
optionalPermissions?: SignInWithYouVersionPermission[];
};

export const UsersAPI = {
/**
* Presents the YouVersion login flow to the user and returns the login result upon completion.
*
* @param params - An object ({@link SignInOptions}) containing an array of permissions that are optional for your app and an array of permissions that are required for a successful login.
* @returns A promise that resolves to the login result as a {@link YouVersionLoginResult} object.
* @param permissions - An array of permissions to request during sign-in.
* @returns A promise that resolves to the login result as a {@link SignInWithYouVersionResult} object.
*/
signIn: ({
requiredPermissions: providedRequired,
optionalPermissions: providedOptional,
}: SignInOptions = {}): Promise<YouVersionLoginResult> => {
const requiredPermissions = providedRequired || [];
const optionalPermissions = providedOptional || [];

return Module.signIn(requiredPermissions, optionalPermissions);
signIn: (
permissions: SignInWithYouVersionPermission[] = [],
): Promise<SignInWithYouVersionResult> => {
return Module.signIn(permissions);
},

/** Signs out the current user by clearing the access token from local storage */
Expand Down
7 changes: 2 additions & 5 deletions src/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
BibleVersion,
HighlightResponse,
LanguageOverview,
YouVersionLoginResult,
SignInWithYouVersionResult,
YouVersionUserInfo,
YouVersionVerseOfTheDay,
} from "./types";
Expand All @@ -15,10 +15,7 @@ declare class RNYouVersionPlatformModule extends NativeModule {

setApiHost(apiHost: string): void;

signIn(
requiredPermissions: string[],
optionalPermissions: string[],
): Promise<YouVersionLoginResult>;
signIn(permissions: string[]): Promise<SignInWithYouVersionResult>;

signOut(): Promise<void>;

Expand Down
11 changes: 8 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { ColorValue } from "react-native";

export type YouVersionLoginResult = {
accessToken: string;
export type SignInWithYouVersionResult = {
accessToken?: string;
permissions: SignInWithYouVersionPermission[];
yvpUserId: string;
yvpUserId?: string;
expiryDate?: string;
refreshToken?: string;
name?: string;
profilePicture?: string;
email?: string;
};

export type SignInWithYouVersionPermission =
Expand Down