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
5 changes: 5 additions & 0 deletions .changeset/two-trees-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": patch
---

fix (in app wallets): error when calling connect for backend strategy due to `document` reference
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export async function backendAuthenticate(args: {
}),
});

if (!res.ok) throw new Error("Failed to generate backend account");
if (!res.ok) {
throw new Error("Failed to generate backend account");
}

return (await res.json()) satisfies AuthStoredTokenWithCookieReturnType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@ export class Auth {
authToken: AuthStoredTokenWithCookieReturnType,
recoveryCode?: string,
): Promise<AuthLoginReturnType> {
await this.preLogin();
// We don't call logout for backend auth because that is handled on the backend where the iframe isn't available to call. Moreover, logout clears the local storage which isn't applicable for backend auth.
if (authToken.storedToken.authProvider !== "Backend") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how can this ever go through iframe auth? shouldnt it always do the enclave flow?

await this.preLogin();
}

const user = await getUserStatus({
authToken: authToken.storedToken.cookieString,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { IframeCommunicator } from "./IframeCommunicator.js";

describe("IframeCommunicator", () => {
// biome-ignore lint/suspicious/noExplicitAny: mock
let mockLocalStorage: any;
let mockContainer: HTMLElement;
let mockIframe: HTMLIFrameElement;

beforeEach(() => {
// Mock localStorage
vi.restoreAllMocks();

mockLocalStorage = {
getAuthCookie: vi.fn().mockResolvedValue("mockAuthCookie"),
getDeviceShare: vi.fn().mockResolvedValue("mockDeviceShare"),
getWalletUserId: vi.fn().mockResolvedValue("mockWalletUserId"),
};
// Mock DOM elements
mockContainer = document.createElement("div");
mockIframe = document.createElement("iframe");
vi.spyOn(document, "createElement").mockReturnValue(mockIframe);
vi.spyOn(document, "getElementById").mockReturnValue(null);
});

it("should create an iframe with correct properties", () => {
new IframeCommunicator({
link: "https://example.com",
baseUrl: "https://example.com",
iframeId: "test-iframe",
container: mockContainer,
localStorage: mockLocalStorage,
clientId: "test-client",
});

expect(document.createElement).toHaveBeenCalledWith("iframe");
expect(mockIframe.id).toBe("test-iframe");
expect(mockIframe.src).toBe("https://example.com/");
expect(mockIframe.style.display).toBe("none");
});

it("should initialize with correct variables", async () => {
const communicator = new IframeCommunicator({
link: "https://example.com",
baseUrl: "https://example.com",
iframeId: "test-iframe",
container: mockContainer,
localStorage: mockLocalStorage,
clientId: "test-client",
});

// biome-ignore lint/complexity/useLiteralKeys: accessing protected method
const vars = await communicator["onIframeLoadedInitVariables"]();

expect(vars).toEqual({
authCookie: "mockAuthCookie",
deviceShareStored: "mockDeviceShare",
walletUserId: "mockWalletUserId",
clientId: "test-client",
partnerId: undefined,
ecosystemId: undefined,
});
});

it("should throw error when calling methods without iframe", async () => {
const temp = global.document;
// @ts-expect-error - Testing undefined document scenario
global.document = undefined;

const communicator = new IframeCommunicator({
link: "https://example.com",
baseUrl: "https://example.com",
iframeId: "test-iframe",
localStorage: mockLocalStorage,
clientId: "test-client",
});

await expect(
communicator.call({
procedureName: "test",
params: {},
}),
).rejects.toThrow("Iframe not found");
global.document = temp;
});

it("should cleanup on destroy", () => {
const communicator = new IframeCommunicator({
link: "https://example.com",
baseUrl: "https://example.com",
iframeId: "test-iframe",
container: mockContainer,
localStorage: mockLocalStorage,
clientId: "test-client",
});

communicator.destroy();

// biome-ignore lint/complexity/useLiteralKeys: accessing protected field
expect(communicator["iframe"]).toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
*/
// biome-ignore lint/suspicious/noExplicitAny: TODO: fix later
export class IframeCommunicator<T extends { [key: string]: any }> {
private iframe: HTMLIFrameElement;
private iframe?: HTMLIFrameElement;
private POLLING_INTERVAL_SECONDS = 1.4;
private iframeBaseUrl;
protected localStorage: ClientScopedStorage;
Expand All @@ -49,7 +49,7 @@
link,
baseUrl,
iframeId,
container = document.body,
container,
onIframeInitialize,
localStorage,
clientId,
Expand All @@ -60,6 +60,10 @@
this.ecosystem = ecosystem;
this.iframeBaseUrl = baseUrl;

if (typeof document === "undefined") {
return;
}
container = container ?? document.body;
// Creating the IFrame element for communication
let iframe = document.getElementById(iframeId) as HTMLIFrameElement | null;
const hrefLink = new URL(link);
Expand Down Expand Up @@ -164,6 +168,11 @@
params: T[keyof T];
showIframe?: boolean;
}) {
if (!this.iframe) {
throw new Error(
"Iframe not found. You are likely calling this from the backend where the DOM is not available.",
);
}
while (!isIframeLoaded.get(this.iframe.src)) {
await sleep(this.POLLING_INTERVAL_SECONDS * 1000);
}
Expand All @@ -182,7 +191,9 @@
if (showIframe) {
// magic number to let modal fade out before hiding it
await sleep(0.1 * 1000);
this.iframe.style.display = "none";
if (this.iframe) {
this.iframe.style.display = "none";
}

Check warning on line 196 in packages/thirdweb/src/wallets/in-app/web/utils/iFrameCommunication/IframeCommunicator.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/wallets/in-app/web/utils/iFrameCommunication/IframeCommunicator.ts#L194-L196

Added lines #L194 - L196 were not covered by tests
}
if (!data.success) {
rej(new Error(data.error));
Expand Down Expand Up @@ -213,6 +224,8 @@
* @internal
*/
destroy() {
isIframeLoaded.delete(this.iframe.src);
if (this.iframe) {
isIframeLoaded.delete(this.iframe.src);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
baseUrl,
}).href,
baseUrl,
container: document.body,
container: typeof document === "undefined" ? undefined : document.body,

Check warning on line 35 in packages/thirdweb/src/wallets/in-app/web/utils/iFrameCommunication/InAppWalletIframeCommunicator.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/wallets/in-app/web/utils/iFrameCommunication/InAppWalletIframeCommunicator.ts#L35

Added line #L35 was not covered by tests
localStorage: new ClientScopedStorage({
storage: webLocalStorage,
clientId,
Expand Down
Loading