-
Notifications
You must be signed in to change notification settings - Fork 117
Bump PHC to 19.3.0 and fix errors not matching PurchasesError #1919
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
8547b75
chore: Update purchases-hybrid-common to 19.3.0
AlvaroBrey d162f82
fix(ios): Carry the error info payload in the rejected NSError
AlvaroBrey 748c89f
ci: Cap jest workers in analyse_js
AlvaroBrey c3e69b2
fix: Normalize errors rejected by the native module
AlvaroBrey 7abc340
fix: Keep what the bridge attaches to native module methods
AlvaroBrey File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { NativeModules } from "react-native"; | ||
| import type { PurchasesError } from "@revenuecat/purchases-typescript-internal"; | ||
|
|
||
| import Purchases from "../src/purchases"; | ||
|
|
||
| /** | ||
| * The shape React Native builds on Android: PromiseImpl sends code, message and | ||
| * userInfo, and RNPurchasesModule passes ErrorContainer.info as that userInfo. | ||
| */ | ||
| function nativeRejection(): Error { | ||
| return Object.assign(new Error("There was a credentials issue."), { | ||
| code: "11", | ||
| message: "There was a credentials issue.", | ||
| userInfo: { | ||
| code: 11, | ||
| message: "There was a credentials issue.", | ||
| readableErrorCode: "InvalidCredentialsError", | ||
| readable_error_code: "InvalidCredentialsError", | ||
| underlyingErrorMessage: "Invalid API Key.", | ||
| }, | ||
| nativeStackAndroid: [], | ||
| }); | ||
| } | ||
|
|
||
| describe("errors rejected by the native module", () => { | ||
| beforeEach(() => { | ||
| NativeModules.RNPurchases.isConfigured.mockResolvedValue(true); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
|
|
||
| it("match the PurchasesError interface", async () => { | ||
| NativeModules.RNPurchases.getCustomerInfo.mockRejectedValueOnce(nativeRejection()); | ||
|
|
||
| const error: PurchasesError = await Purchases.getCustomerInfo().catch((caught: unknown) => caught); | ||
|
|
||
| expect(error.code).toBe("11"); | ||
| expect(error.readableErrorCode).toBe("InvalidCredentialsError"); | ||
| expect(error.underlyingErrorMessage).toBe("Invalid API Key."); | ||
| expect(error.userInfo.readableErrorCode).toBe("InvalidCredentialsError"); | ||
| expect(error.userCancelled).toBe(false); | ||
| }); | ||
|
|
||
| it("stay real Errors", async () => { | ||
| NativeModules.RNPurchases.getCustomerInfo.mockRejectedValueOnce(nativeRejection()); | ||
|
|
||
| const error = await Purchases.getCustomerInfo().catch((caught: unknown) => caught); | ||
|
|
||
| expect(error).toBeInstanceOf(Error); | ||
| expect(typeof (error as Error).stack).toBe("string"); | ||
| }); | ||
|
|
||
| it("keep the fields userInfo already carried", async () => { | ||
| NativeModules.RNPurchases.getCustomerInfo.mockRejectedValueOnce(nativeRejection()); | ||
|
|
||
| const error: PurchasesError = await Purchases.getCustomerInfo().catch((caught: unknown) => caught); | ||
| const userInfo = error.userInfo as Record<string, unknown>; | ||
|
|
||
| expect(userInfo.underlyingErrorMessage).toBe("Invalid API Key."); | ||
| expect(userInfo.readable_error_code).toBe("InvalidCredentialsError"); | ||
| }); | ||
|
|
||
| it("leave successful calls alone", async () => { | ||
| NativeModules.RNPurchases.getCustomerInfo.mockResolvedValueOnce(global.customerInfoStub); | ||
|
|
||
| await expect(Purchases.getCustomerInfo()).resolves.toBeTruthy(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { normalizingRejections } from "../src/normalizingRejections"; | ||
|
|
||
| // React Native's bridge generates each method as a named function and tags it with the | ||
| // call type (NativeModules.js: `fn.type = type`), so the wrapper has to pass both on. | ||
| function bridgeMethod(result: () => unknown) { | ||
| const fn = function promiseMethodWrapper() { | ||
| return result(); | ||
| }; | ||
| (fn as typeof fn & { type: string }).type = "promise"; | ||
| return fn; | ||
| } | ||
|
|
||
| function moduleWith(result: () => unknown) { | ||
| return normalizingRejections({ getCustomerInfo: bridgeMethod(result), addListener: () => undefined }); | ||
| } | ||
|
|
||
| describe("the native module wrapper", () => { | ||
| it("keeps what the bridge puts on its methods", () => { | ||
| const wrapped = moduleWith(() => Promise.resolve({})); | ||
|
|
||
| expect(wrapped.getCustomerInfo.name).toBe("promiseMethodWrapper"); | ||
| expect((wrapped.getCustomerInfo as unknown as { type: string }).type).toBe("promise"); | ||
| expect(Reflect.ownKeys(wrapped.getCustomerInfo)).toEqual( | ||
| Reflect.ownKeys(bridgeMethod(() => Promise.resolve({}))), | ||
| ); | ||
| }); | ||
|
|
||
| it("normalizes a rejection", async () => { | ||
| const wrapped = moduleWith(() => | ||
| Promise.reject(Object.assign(new Error("nope"), { code: "11", userInfo: { readableErrorCode: "Bad" } })), | ||
| ); | ||
|
|
||
| const error = await wrapped.getCustomerInfo().catch((caught: unknown) => caught); | ||
|
|
||
| expect((error as { readableErrorCode: string }).readableErrorCode).toBe("Bad"); | ||
| }); | ||
|
|
||
| it("leaves a non-promise return alone", () => { | ||
| const wrapped = normalizingRejections({ addListener: bridgeMethod(() => "sync value") }); | ||
|
|
||
| expect(wrapped.addListener()).toBe("sync value"); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { normalizePurchasesError } from "@revenuecat/purchases-typescript-internal"; | ||
|
|
||
| // The returned promise is chained, never copied: enumerating a TurboModule | ||
| // promise's own properties throws inside Hermes. | ||
| export function normalizingRejections<T extends object>(module: T): T { | ||
| return new Proxy(module, { | ||
| get(target, property, receiver) { | ||
| const value = Reflect.get(target, property, receiver); | ||
| if (typeof value !== "function") { | ||
| return value; | ||
| } | ||
| // An apply trap keeps what the bridge puts on the method it generated, such as its | ||
| // name and React Native's own call-type marker. | ||
| return new Proxy(value as (...callArgs: unknown[]) => unknown, { | ||
| apply: (method, thisArg, args) => { | ||
| const result = Reflect.apply(method, thisArg, args); | ||
| return result instanceof Promise | ||
| ? result.catch((error: unknown) => { | ||
| throw normalizePurchasesError(error); | ||
| }) | ||
| : result; | ||
| }, | ||
| }); | ||
| }, | ||
| }); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because I was seeing crashes on CI due to too many parallel workers. I took the maxWorkers approach from some of our other repos