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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"build:sdk": "cd ./wander-connect-sdk/ && pnpm build",
"build:assets": "cp -r assets/forge dist/assets/forge && cp -r assets/animation dist/assets/animation",
"build:wallet-api": "vite build --config vite.wallet-config.js",
"clean": "rm -rf yarn.lock node_modules",
"clean": "rm -rf .plasmo build",
"nuke": "yarn clean && rm -rf yarn.lock node_modules",
"fmt": "prettier --write .",
"fmt:check": "prettier --check .",
Expand Down Expand Up @@ -142,7 +142,7 @@
"jsdom": "^26.0.0",
"lint-staged": ">=10",
"path-browserify": "^1.0.1",
"plasmo": "0.90.3",
"plasmo": "0.90.5",
"prettier": "^3.5.3",
"querystring-es3": "^0.2.1",
"semantic-release": "^23.0.0",
Expand Down
19 changes: 19 additions & 0 deletions src/api/background/background-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { log, LOG_GROUP } from "~utils/log/log.utils";
import { isomorphicOnMessage } from "~isomorphic-messaging";
import { handleAuthStateChange } from "./handlers/storage/auth-state-change/auth-state-change.handler";
import { initInactivityTracking } from "~utils/inactivity/inactivity.utils";
import { getAuthPopupWindowTabID, getLastAuthID, getPopupWindowID, resetPopupTabID } from "~utils/auth/auth.utils";

export function setupBackgroundService() {
log(
Expand All @@ -37,6 +38,24 @@ export function setupBackgroundService() {
// Watch for API call and chunk messages:
isomorphicOnMessage("api_call", handleApiCallMessage);
isomorphicOnMessage("chunk", handleChunkMessage);
isomorphicOnMessage("auth_incoming_check" as any, () => {
return getLastAuthID();
});

console.log("TEST 4");

chrome.windows.onCreated.addListener((window) => {
console.log("ON CREATED", window.id, getPopupWindowID());
});

chrome.windows.onRemoved.addListener((closedWindowId) => {
console.log("ON REMOVED", closedWindowId, getPopupWindowID());

if (closedWindowId === getPopupWindowID()) {
console.log("POPUP CLOSED");
resetPopupTabID();
}
});

/*
if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1") {
Expand Down
2 changes: 1 addition & 1 deletion src/api/foreground/foreground-setup-wallet-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function injectWanderWalletAPI(targetWindow: Window = window, embed
// TODO: Can we get the right type here?:
const walletAPI = {
walletName: IS_EMBEDDED_APP ? "Wander Connect" : "ArConnect",
walletVersion: version,
walletVersion: "1.28.0",
events,
} as const;

Expand Down
3 changes: 2 additions & 1 deletion src/utils/auth/auth.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { ModuleAppData } from "~api/background/background-modules";

export const AUTH_POPUP_REQUEST_WAIT_MS = 1000 as const;
export const AUTH_POPUP_REQUEST_ARRIVAL_WINDOW_MS = 1000 as const;
export const AUTH_POPUP_CLOSING_DELAY_MS = process.env.NODE_ENV === "development" ? (5000 as const) : (0 as const);
// export const AUTH_POPUP_CLOSING_DELAY_MS = process.env.NODE_ENV === "development" ? (5000 as const) : (0 as const);
export const AUTH_POPUP_CLOSING_DELAY_MS = 0;
export const AUTH_POPUP_UNLOCK_REQUEST_TTL_MS = 900000 as const; // 15 min.

export const DEFAULT_MODULE_APP_DATA = {
Expand Down
77 changes: 57 additions & 20 deletions src/utils/auth/auth.provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { Chunk } from "~api/modules/sign/chunks";
import { defaultGateway } from "~gateways/gateway";
import Arweave from "arweave";
import { bytesFromChunks, constructTransaction, type SplitTransaction } from "~api/modules/sign/transaction_builder";
import { isomorphicOnMessage } from "~isomorphic-messaging";
import { isomorphicOnMessage, isomorphicSendMessage } from "~isomorphic-messaging";
import type { IBridgeMessage } from "@arconnect/webext-bridge";
import { log, LOG_GROUP } from "~utils/log/log.utils";
import { isError } from "~utils/error/error.utils";
Expand Down Expand Up @@ -60,12 +60,13 @@ export function AuthRequestsProvider({ children }: PropsWithChildren) {
});
}, []);

const closeAuthPopup = useCallback((delay: number = 0) => {
function closeOrClear() {
if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1") {
setAuthRequestContextState(AUTH_REQUESTS_CONTEXT_INITIAL_STATE);
const closeAuthPopup = useCallback(
(delay: number, lastAuthID: string) => {
async function closeOrClear() {
if (import.meta.env?.VITE_IS_EMBEDDED_APP === "1") {
setAuthRequestContextState(AUTH_REQUESTS_CONTEXT_INITIAL_STATE);

/*
/*

// This message below is already sent when the last request is handled / completed:

Expand All @@ -90,21 +91,45 @@ export function AuthRequestsProvider({ children }: PropsWithChildren) {
// here.

*/
} else {
window.top.close();
} else {
console.log("Request last ID");

const lastAuthIDFromBackground = await isomorphicSendMessage({
destination: "background",
messageId: "auth_incoming_check" as any,
data: null,
});

console.log({ lastAuthIDFromBackground, lastAuthID });

console.trace("DEBUG CLOSE");

if ((window as any).noclose) {
debugger;
}

if (lastAuthIDFromBackground !== lastAuthID) {
console.log("CLOSE ABORTED");

return;
}

window.top.close();
}
}
}

if (delay > 0) {
const timeoutID = setTimeout(closeOrClear, delay);
if (delay > 0) {
const timeoutID = setTimeout(closeOrClear, delay);

return () => clearTimeout(timeoutID);
}
return () => clearTimeout(timeoutID);
}

closeOrClear();
closeOrClear();

return () => {};
}, []);
return () => {};
},
[authRequests],
);

const completeAuthRequest = useCallback(
async (authID: string, data: any) => {
Expand Down Expand Up @@ -232,6 +257,8 @@ export function AuthRequestsProvider({ children }: PropsWithChildren) {
log(LOG_GROUP.AUTH, "Auth popup initialized. Waiting for AuthRequests");

isomorphicOnMessage("auth_request", (authRequestMessage) => {
console.log("auth_request");

log(LOG_GROUP.AUTH, "auth_request =", authRequestMessage);

// UnlockAuthRequests are not enqueued as those are simply used to open the popup to prompt users to enter their
Expand Down Expand Up @@ -317,7 +344,7 @@ export function AuthRequestsProvider({ children }: PropsWithChildren) {

if (pendingRequestsCount === 0 && authRequests.length > 0) {
// All tabs that sent AuthRequest also got closed/reloaded/disconnected, so close the popup immediately:
closeAuthPopup();
closeAuthPopup(0, authRequests[authRequests.length - 1].authID);
}

// TODO: Consider automatically selecting the next pending AuthRequest.
Expand All @@ -344,6 +371,8 @@ export function AuthRequestsProvider({ children }: PropsWithChildren) {
isomorphicOnMessage("auth_chunk", ({ sender, data }) => {
if (sender.context !== "background") return;

console.log("auth_chunk");

const { type, collectionID } = data;

log(LOG_GROUP.CHUNKS, `auth_chunk(type ="${type}", collectionID = "${collectionID})"`);
Expand Down Expand Up @@ -432,12 +461,20 @@ export function AuthRequestsProvider({ children }: PropsWithChildren) {
const isEmbedded = import.meta.env?.VITE_IS_EMBEDDED_APP === "1";
const hasAuthRequests = authRequests.length > 0;
const isDone = hasAuthRequests && authRequests.every((authRequest) => authRequest.status !== "pending");
const lastAuthID = authRequests[authRequests.length - 1].authID;

console.log({
isEmbedded,
hasAuthRequests,
isDone,
authRequests,
});

if (isDone) {
// Close the window if the last request has been handled:
// TODO: Add setting to decide whether this closes automatically or stays open in a "done" state.

clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_CLOSING_DELAY_MS);
clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_CLOSING_DELAY_MS, lastAuthID);
} else if (!isEmbedded) {
const isLocked = !(await getDecryptionKey());

Expand All @@ -449,11 +486,11 @@ export function AuthRequestsProvider({ children }: PropsWithChildren) {
// If the wallet is locked, the user has `AUTH_POPUP_UNLOCK_REQUEST_TTL_MS` (15 minutes) to unlock it before
// we close it automatically. While that's happening, AuthRequest might or might not be in this provide
// already:
clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_UNLOCK_REQUEST_TTL_MS);
clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_UNLOCK_REQUEST_TTL_MS, lastAuthID);
} else if (!hasAuthRequests && !isInRequestArrivalWindow) {
// Once the wallet is unlocked, we close the popup if an AuthRequest doesn't arrive in less than
// `AUTH_POPUP_REQUEST_WAIT_MS` (1 second) but only if the component is not in the initial request arrival window:
clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_REQUEST_WAIT_MS);
clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_REQUEST_WAIT_MS, lastAuthID);
}
}
}
Expand Down
68 changes: 47 additions & 21 deletions src/utils/auth/auth.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,21 @@ type PopupCallback = (popupTabID: number) => void;
let popupUpdatedCallbacks: PopupCallback[] = [];
let popupClosedCallbacks: PopupCallback[] = [];

let POPUP_WINDOW_ID = -1;
let POPUP_TAB_ID = -1;

function setPopupTabID(popupTabID: number) {
log(LOG_GROUP.AUTH, "setPopupTabID =", popupTabID);
function setPopupTabID(window: browser.Windows.Window | null) {
const popupWindowId = window ? window.id : -1;
const popupTabId = window ? window.tabs[0].id : -1;

POPUP_TAB_ID = popupTabID;
log(LOG_GROUP.AUTH, { popupWindowId, popupTabId });

if (popupTabID === -1) {
POPUP_WINDOW_ID = popupWindowId;
POPUP_TAB_ID = popupTabId;

if (popupTabId === -1) {
popupClosedCallbacks.forEach((cb) => {
cb(popupTabID);
cb(popupTabId);
});

popupClosedCallbacks = [];
Expand All @@ -47,12 +52,16 @@ function setPopupTabID(popupTabID: number) {
}

popupUpdatedCallbacks.forEach((cb) => {
cb(popupTabID);
cb(popupTabId);
});

popupUpdatedCallbacks = [];
}

export function getPopupWindowID() {
return POPUP_WINDOW_ID;
}

function onPopupTabUpdated(cb: PopupCallback) {
if (POPUP_TAB_ID !== -1) return cb(POPUP_TAB_ID);

Expand All @@ -70,7 +79,7 @@ export function onPopupClosed(cb: PopupCallback) {
}

export function resetPopupTabID() {
setPopupTabID(-1);
setPopupTabID(null);
}

export function getCachedAuthPopupWindowTabID() {
Expand Down Expand Up @@ -100,13 +109,21 @@ export async function requestUserAuthorization<T = any>(
) {
log(LOG_GROUP.AUTH, `requestUserAuthorization("${authRequestData.type}")`);

// TODO: Should we wrap both in retry in case the popup was already closed?

// create the popup
const { authID, popupWindowTabID } = await createAuthPopup(authRequestData, moduleAppData);

// wait for the results from the popup
return await getPopupResponse<T>(authID, popupWindowTabID);
}

let lastAuthID = "";

export function getLastAuthID() {
return lastAuthID;
}

/**
* Create or reuse an authenticator popup to handle an `AuthRequest`.
*
Expand All @@ -115,6 +132,13 @@ export async function requestUserAuthorization<T = any>(
* @returns ID of the authentication
*/
export async function createAuthPopup(authRequestData: null | AuthRequestData, moduleAppData: ModuleAppData) {
// Generate an unique id for the authentication to be checked later:
const authID = nanoid();
if (authRequestData) {
console.log("Update ID =", authID);
lastAuthID = authID;
}

const unlock = await popupMutex.lock();

try {
Expand Down Expand Up @@ -146,6 +170,8 @@ export async function createAuthPopup(authRequestData: null | AuthRequestData, m
// TODO: In Embedded we are already in the right window, so no need to create one, just skip
// this and make the authRequestData make it to the AuthRequestsProvider.

console.log("popupWindowTab", popupWindowTab);

try {
if (!popupWindowTab) {
// TODO: To center this, the injected tab should send the center or dimensions of the screen:
Expand All @@ -160,10 +186,12 @@ export async function createAuthPopup(authRequestData: null | AuthRequestData, m
height: 720,
});

setPopupTabID(window.tabs[0].id);
setPopupTabID(window);
} else {
log(LOG_GROUP.AUTH, "reusePopupTabID =", POPUP_TAB_ID);

// TODO: Use drawAttention instead of focused if the tab that sent this is not active.

await browser.windows.update(popupWindowTab.windowId, {
focused: true,
});
Expand All @@ -172,12 +200,7 @@ export async function createAuthPopup(authRequestData: null | AuthRequestData, m
console.warn(`Could not ${popupWindowTab ? "focus" : "open"} "tabs/auth.html":`, err);
}

let authID: string | undefined;

if (authRequestData) {
// Generate an unique id for the authentication to be checked later:
authID = nanoid();

log(LOG_GROUP.AUTH, `isomorphicSendMessage(authID = "${authID}", tabId = ${POPUP_TAB_ID})`);

await isomorphicSendMessage({
Expand Down Expand Up @@ -216,6 +239,14 @@ function addAuthResultListener<T>(authID: string, popupWindowTabID: number, fn:

if (authResultCallbacks.size === 1) {
isomorphicOnMessage("auth_result", ({ sender, data }) => {
const authResultCallback = authResultCallbacks.get(data.authID);

if (!authResultCallback) {
console.warn(`authID = ${data.authID} doesn't have an "auth_result" listener`);

return;
}

// validate sender by it's tabId
if (sender.tabId !== popupWindowTabID) {
console.warn(
Expand All @@ -225,14 +256,6 @@ function addAuthResultListener<T>(authID: string, popupWindowTabID: number, fn:
return;
}

const authResultCallback = authResultCallbacks.get(data.authID);

if (!authResultCallback) {
console.warn(`authID = ${data.authID} doesn't have an "auth_result" listener`);

return;
}

authResultCallback(data);
});
}
Expand All @@ -258,6 +281,9 @@ export function getPopupResponse<T>(authID: string, popupWindowTabID: number) {
reject(ERR_MSG_UNLOCK_TIMEOUT);
}, AUTH_POPUP_UNLOCK_REQUEST_TTL_MS);

// TODO: If the popup was closed in the race condition, do we return a response?
// TODO:

addAuthResultListener<AuthSuccessResult<T>>(authID, popupWindowTabID, (data) => {
stopKeepAlive(authID);
clearTimeout(timeoutID);
Expand Down
Loading