Skip to content
Draft
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: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

## [4.0.0]

### Removed

- Custom onLoadUrlInBrowser and onLoadUrlInBrowserError props

### Changed

- Now opening oauth tabs inside the app instead of in a separate browser. This simplifies the configuration process and makes it easier for the user to get back to the app in case of failures.

## [3.0.0]

### Removed

- No longer rendering the widgets inside of a SafeAreaView. Users of the widgets need to handle this.

### Updated
### Changed

- Stopped using the deprecated react-native SafeAreaView in favor of react-native-safe-area-context

Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
collectCoverageFrom: ["src/**/**"],
setupFiles: ["./test/setup.ts"],
setupFilesAfterEnv: ["./test/mocks/setup.ts", "./test/mocks/react_native_webview.ts"],
testRegex: ["_test\\.[jt]sx?$"],
testRegex: [".test\\.[jt]sx?$"],
testPathIgnorePatterns: [
"<rootDir>/node_modules",
"<rootDir>/dist",
Expand Down
3,668 changes: 3,394 additions & 274 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@mxenabled/react-native-widget-sdk",
"description": "MX React Native Widget SDK",
"version": "3.0.0",
"version": "4.0.0",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -32,6 +32,7 @@
},
"dependencies": {
"@mxenabled/widget-post-message-definitions": "^1.4.0",
"expo-web-browser": "^13.0.0",
"react-native-base64": "^0.2.1",
"url": "^0.11.0"
},
Expand Down
93 changes: 93 additions & 0 deletions src/components/ConnectWidgets.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { render, waitFor } from "@testing-library/react-native"
import { ConnectWidget } from "./ConnectWidgets"
import { fullWidgetComponentTestSuite } from "./widgets.test"
import { triggerUrlChange } from "../../test/mocks/react_native"

jest.mock("expo-web-browser", () => {
return {
openAuthSessionAsync: jest.fn().mockResolvedValue({ type: "success" }),
}
})

describe("ConnectWidget", () => {
fullWidgetComponentTestSuite(ConnectWidget)

describe("OAuth", () => {
const testCases: {
label: string
url: string
check: (msg: string) => void
}[] = [
{
label:
"an OAuth deeplink triggers a post message to the web view (public URL with matching host)",
url: "appscheme://oauth_complete?member_guid=MBR-123&status=success",
check: (msg) => expect(msg).toContain("MBR-123"),
},
{
label:
"an OAuth deeplink triggers a post message to the web view (local URL with matching path)",
url: "exp://127.0.0.1:19000/--/oauth_complete?member_guid=MBR-123&status=success",
check: (msg) => expect(msg).toContain("MBR-123"),
},
{
label:
"an OAuth deeplink triggers a post message to the web view (public URL with matching path)",
url: "exp://exp.host/@community/with-webbrowser-redirect/--/oauth_complete?member_guid=MBR-123&status=success",
check: (msg) => expect(msg).toContain("MBR-123"),
},
{
label:
"an OAuth success deeplink includes the right status (public URL with matching host)",
url: "appscheme://oauth_complete?member_guid=MBR-123&status=success",
check: (msg) => expect(msg).toContain("oauthComplete/success"),
},
{
label: "an OAuth success deeplink includes the right status (local URL with matching path)",
url: "exp://127.0.0.1:19000/--/oauth_complete?member_guid=MBR-123&status=success",
check: (msg) => expect(msg).toContain("oauthComplete/success"),
},
{
label:
"an OAuth success deeplink includes the right status (public URL with matching path)",
url: "exp://exp.host/@community/with-webbrowser-redirect/--/oauth_complete?member_guid=MBR-123&status=success",
check: (msg) => expect(msg).toContain("oauthComplete/success"),
},
{
label:
"an OAuth failure deeplink includes the right status (public URL with matching host)",
url: "appscheme://oauth_complete?member_guid=MBR-123&status=error",
check: (msg) => expect(msg).toContain("oauthComplete/error"),
},
{
label: "an OAuth failure deeplink includes the right status (local URL with matching path)",
url: "exp://127.0.0.1:19000/--/oauth_complete?member_guid=MBR-123&status=error",
check: (msg) => expect(msg).toContain("oauthComplete/error"),
},
{
label:
"an OAuth failure deeplink includes the right status (public URL with matching path)",
url: "exp://exp.host/@community/with-webbrowser-redirect/--/oauth_complete?member_guid=MBR-123&status=error",
check: (msg) => expect(msg).toContain("oauthComplete/error"),
},
]

testCases.forEach((testCase) => {
test(testCase.label, async () => {
expect.assertions(1)

const component = render(
<ConnectWidget
url="https://widgets.moneydesktop.com/md/..."
sendOAuthPostMessage={(_ref, msg) => {
testCase.check(msg)
}}
/>,
)

await waitFor(() => component.findByTestId("widget_webview"))
triggerUrlChange(testCase.url)
})
})
})
})
4 changes: 1 addition & 3 deletions src/components/ConnectWidgets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
} from "@mxenabled/widget-post-message-definitions"

import { Type, SsoUrlProps, ConnectWidgetConfigurationProps } from "../sso"
import { LoadUrlInBrowserProps } from "./load_url_in_browser"
import { makeDefaultConnectOnOAuthRequested } from "./oauth"
import { makeWidgetComponentWithDefaults } from "./make_component"
import { useOAuthDeeplink, OAuthProps } from "./oauth"
Expand All @@ -13,7 +12,6 @@ import { useWidgetRendererWithRef, StylingProps } from "./renderer"
export type ConnectWidgetProps = SsoUrlProps &
StylingProps &
OAuthProps &
LoadUrlInBrowserProps &
ConnectPostMessageCallbackProps<string> &
ConnectWidgetConfigurationProps &
JSX.IntrinsicAttributes
Expand All @@ -25,7 +23,7 @@ export const ConnectVerificationWidget = makeWidgetComponentWithDefaults(Connect

export function ConnectWidget(props: ConnectWidgetProps) {
props = {
onOAuthRequested: makeDefaultConnectOnOAuthRequested(props),
onOAuthRequested: makeDefaultConnectOnOAuthRequested(),
...props,
}

Expand Down
32 changes: 32 additions & 0 deletions src/components/loadUrlInBrowser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as ExpoWebBrowser from "expo-web-browser"
import { loadUrlInBrowser } from "./loadUrlInBrowser"

jest.mock("expo-web-browser", () => {
return {
openAuthSessionAsync: jest.fn(),
}
})

describe("loadUrlInBrowser", () => {
it("should call openAuthSessionAsync with the provided URL", async () => {
// eslint-disable-next-line @typescript-eslint/no-extra-semi
;(ExpoWebBrowser.openAuthSessionAsync as jest.Mock).mockResolvedValue({ type: "success" })

const externalUrl = "https://example.com/oauth"
await loadUrlInBrowser(externalUrl)

expect(ExpoWebBrowser.openAuthSessionAsync).toHaveBeenCalledWith(externalUrl)
})

it("should swallow errors", async () => {
// eslint-disable-next-line @typescript-eslint/no-extra-semi
;(ExpoWebBrowser.openAuthSessionAsync as jest.Mock).mockRejectedValueOnce(
new Error("Network error"),
)

const externalUrl = "https://example.com/oauth"

expect(ExpoWebBrowser.openAuthSessionAsync).toHaveBeenCalledWith(externalUrl)
expect(() => loadUrlInBrowser(externalUrl)).not.toThrow()
})
})
9 changes: 9 additions & 0 deletions src/components/loadUrlInBrowser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as WebBrowser from "expo-web-browser"

export async function loadUrlInBrowser(url: string) {
try {
await WebBrowser.openAuthSessionAsync(url)
} catch (error) {
console.log(`Error loading ${url}: ${error}`)
}
}
36 changes: 0 additions & 36 deletions src/components/load_url_in_browser.ts

This file was deleted.

6 changes: 3 additions & 3 deletions src/components/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { ConnectOAuthRequestedPayload } from "@mxenabled/widget-post-message-def

import { WebViewRef } from "./webview"
import { onUrlChange } from "../platform/deeplink"
import { loadUrlInBrowser, LoadUrlInBrowserProps } from "./load_url_in_browser"
import { loadUrlInBrowser } from "./loadUrlInBrowser"

export function makeDefaultConnectOnOAuthRequested(props: LoadUrlInBrowserProps) {
export function makeDefaultConnectOnOAuthRequested() {
return function ({ url }: ConnectOAuthRequestedPayload) {
loadUrlInBrowser(url, props)
loadUrlInBrowser(url)
}
}

Expand Down
6 changes: 1 addition & 5 deletions src/components/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { WebView } from "react-native-webview"
import { Payload } from "@mxenabled/widget-post-message-definitions"

import { Props, useSsoUrl } from "../sso"
import { loadUrlInBrowser, LoadUrlInBrowserProps } from "./load_url_in_browser"
import { makeRequestInterceptor } from "./request_interceptor"
import { useFullscreenStyles } from "./screen_dimensions"
import { sdkVersion } from "../version"
Expand All @@ -15,7 +14,7 @@ export type StylingProps = {
}

type MaybeWebViewRef = MutableRefObject<WebView | null>
type BaseProps<Configuration> = Props<Configuration> & StylingProps & LoadUrlInBrowserProps
type BaseProps<Configuration> = Props<Configuration> & StylingProps

export function useWidgetRenderer<Configuration>(
props: BaseProps<Configuration>,
Expand Down Expand Up @@ -43,9 +42,6 @@ export function useWidgetRendererWithRef<Configuration>(
onIntercept: (url) => {
dispatchEvent(url, props)
},
onLoadUrlInBrowser: (url) => {
loadUrlInBrowser(url, props)
},
})

const setReactNativeSDKVersionOnWindow = `
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ import {
LoadPayload,
dispatchConnectLocationChangeEvent as handler,
} from "@mxenabled/widget-post-message-definitions"
import { makeRequestInterceptor } from "./request_interceptor"
import * as ExpoWebBrowser from "expo-web-browser"

import { loadUrlInBrowser } from "../../src/components/load_url_in_browser"
import { makeRequestInterceptor } from "../../src/components/request_interceptor"
jest.mock("expo-web-browser", () => {
return {
openAuthSessionAsync: jest.fn().mockResolvedValue({ type: "success" }),
}
})
jest.mock("react-native")

const makeNavigationEvent = (url: string) =>
({
Expand Down Expand Up @@ -57,46 +63,20 @@ describe("makeRequestInterceptor", () => {

expect(fn(req)).toBe(false)
})
})

describe("callbacks", () => {
test("calls the onLoadUrlInBrowser callback when loading a valid non post message url", () => {
expect.assertions(1)

const newUrl = "https://mx.com/page2"
const callbacks = {
onLoadUrlInBrowser: (url: string) => expect(url).toBe(newUrl),
}

test("it calls loadUrlInBrowser for external urls", () => {
const fn = makeRequestInterceptor("https://mx.com/", "appscheme", {
onIntercept: (url) => handler(url, {}),
onLoadUrlInBrowser: (url) => loadUrlInBrowser(url, callbacks),
})
const req = makeNavigationEvent(newUrl)

fn(req)
})

test("calls the onLoadUrlInBrowserError callback when loading a url causes an error", () => {
expect.assertions(1)

const newUrl = "https://mx.com/page2"
const callbacks = {
onLoadUrlInBrowser: (_url: string) => {
throw new Error("not today")
},
onLoadUrlInBrowserError: (url: string, _error: Error) => expect(url).toBe(newUrl),
}

const fn = makeRequestInterceptor("https://mx.com/", "appscheme", {
onIntercept: (url) => handler(url, {}),
onLoadUrlInBrowser: (url) => loadUrlInBrowser(url, callbacks),
})
const req = makeNavigationEvent(newUrl)
const externalUrl = "https://example.com/page"
const req = makeNavigationEvent(externalUrl)

fn(req)
expect(ExpoWebBrowser.openAuthSessionAsync).toHaveBeenCalledWith(externalUrl)
})
})

describe("callbacks", () => {
test("valid mx/load message calls onLoad callback", () => {
expect.assertions(1)

Expand Down
5 changes: 3 additions & 2 deletions src/components/request_interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { WebViewNavigation } from "react-native-webview"
import { parse as parseUrl } from "url"

import { loadUrlInBrowser } from "./loadUrlInBrowser"

export enum Action {
LoadInApp,
LoadInBrowser,
Expand All @@ -9,7 +11,6 @@ export enum Action {

type Callbacks = {
onIntercept?: (url: string) => void
onLoadUrlInBrowser?: (url: string) => void
}

class Interceptor {
Expand Down Expand Up @@ -53,7 +54,7 @@ export function makeRequestInterceptor(
return false

case Action.LoadInBrowser:
callbacks.onLoadUrlInBrowser?.(request.url)
loadUrlInBrowser(request.url)
return false
}
}
Expand Down
Loading