Skip to content

Commit

Permalink
feat: use tabs api if window apis are not available (#2882)
Browse files Browse the repository at this point in the history
* feat: add support for tabs api as an alternate in prompt opening logic of extension

* chore: simplify createPopup and createTab functions

* fix: simplify

* fix: add fix for opera

---------

Co-authored-by: René Aaron <[email protected]>
Co-authored-by: René Aaron <[email protected]>
  • Loading branch information
3 people authored Dec 12, 2023
1 parent 2e31bcf commit ec419a5
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 80 deletions.
129 changes: 50 additions & 79 deletions src/common/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import browser, { Runtime } from "webextension-polyfill";
import { ABORT_PROMPT_ERROR } from "~/common/constants";
import { getPosition as getWindowPosition } from "~/common/utils/window";
import { ConnectorTransaction } from "~/extension/background-script/connectors/connector.interface";
import type { DeferredPromise, OriginData, OriginDataInternal } from "~/types";
import { createPromptTab, createPromptWindow } from "../utils/window";

const utils = {
base64ToHex: (str: string) => {
Expand Down Expand Up @@ -79,95 +79,66 @@ const utils = {
"prompt.html"
)}?${urlParams.toString()}`;

const windowWidth = 400;
const windowHeight = 600;
// Window APIs might not be available on mobile browsers
const useWindow = !!browser.windows;

const { top, left } = await getWindowPosition(windowWidth, windowHeight);
// Either API yields a tabId
const tabId = useWindow
? await createPromptWindow(url)
: await createPromptTab(url);

return new Promise((resolve, reject) => {
browser.windows
.create({
url: url,
type: "popup",
width: windowWidth,
height: windowHeight,
top: top,
left: left,
})
.then((window) => {
let closeWindow = true; // by default we call remove.window (except the browser forces this prompt to open in a tab)
let tabId: number | undefined;
if (window.tabs) {
tabId = window.tabs[0].id;
}
const onMessageListener = (
responseMessage: {
response?: unknown;
error?: string;
data: Type;
},
sender: Runtime.MessageSender
) => {
if (
responseMessage &&
responseMessage.response &&
sender.tab &&
sender.tab.id === tabId &&
sender.tab.windowId
) {
// Remove the event listener as we are about to close the tab
browser.tabs.onRemoved.removeListener(onRemovedListener);

// Kiwi Browser opens the prompt in the same window (there are only tabs on mobile browsers)
// Find the currently active tab to validate messages
if (window.tabs && window.tabs?.length > 1) {
tabId = window.tabs?.find((x) => x.active)?.id;
closeWindow = false; // we'll only remove the tab and not the window further down
// Use window APIs for removing the window as Opera doesn't
// close the window if you remove the last tab (e.g. in popups)
let closePromise;
if (useWindow) {
closePromise = browser.windows.remove(sender.tab.windowId);
} else {
closePromise = browser.tabs.remove(tabId);
}

// Re-focus the popup after 2 seconds to mitigate the problem of lost popups
// (e.g. when a user clicks the website)
setTimeout(() => {
if (!window.id) return;

browser.windows.update(window.id, {
focused: true,
});
}, 2100);

const onMessageListener = (
responseMessage: {
response?: unknown;
error?: string;
data: Type;
},
sender: Runtime.MessageSender
) => {
if (
responseMessage &&
responseMessage.response &&
sender.tab &&
sender.tab.id === tabId &&
sender.tab.windowId
) {
browser.tabs.onRemoved.removeListener(onRemovedListener);
// if the window was opened as tab we remove the tab
// otherwise if a window was opened we have to remove the window.
// Opera fails to close the window with tabs.remove - it fails with: "Tabs cannot be edited right now (user may be dragging a tab)"
let closePromise;
if (closeWindow) {
closePromise = browser.windows.remove(sender.tab.windowId);
} else {
closePromise = browser.tabs.remove(sender.tab.id as number); // as number only for TS - we check for sender.tab.id in the if above
}

return closePromise.then(() => {
// in the future actual "remove" (closing prompt) will be moved to component for i.e. budget flow
// https://github.com/getAlby/lightning-browser-extension/issues/1197
if (responseMessage.error) {
return reject(new Error(responseMessage.error));
} else {
return resolve(responseMessage);
}
});
return closePromise.then(() => {
// in the future actual "remove" (closing prompt) will be moved to component for i.e. budget flow
// https://github.com/getAlby/lightning-browser-extension/issues/1197
if (responseMessage.error) {
return reject(new Error(responseMessage.error));
} else {
return resolve(responseMessage);
}
};
});
}
};

const onRemovedListener = (tid: number) => {
if (tabId === tid) {
browser.runtime.onMessage.removeListener(onMessageListener);
reject(new Error(ABORT_PROMPT_ERROR));
}
};
const onRemovedListener = (tid: number) => {
if (tabId === tid) {
browser.runtime.onMessage.removeListener(onMessageListener);
reject(new Error(ABORT_PROMPT_ERROR));
}
};

browser.runtime.onMessage.addListener(onMessageListener);
browser.tabs.onRemoved.addListener(onRemovedListener);
});
browser.runtime.onMessage.addListener(onMessageListener);
browser.tabs.onRemoved.addListener(onRemovedListener);
});
},

getBoostagramFromInvoiceCustomRecords: (
custom_records: ConnectorTransaction["custom_records"] | undefined
) => {
Expand Down
29 changes: 28 additions & 1 deletion src/common/utils/window.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
import browser from "webextension-polyfill";

export async function getPosition(
export async function createPromptWindow(url: string): Promise<number> {
const windowWidth = 400;
const windowHeight = 600;

const { top, left } = await getPosition(windowWidth, windowHeight);

const popupOptions: browser.Windows.CreateCreateDataType = {
url: url,
type: "popup",
width: windowWidth,
height: windowHeight,
top: top,
left: left,
};
const result = await browser.windows.create(popupOptions);
return result.tabs! && result.tabs[0].id!;
}

export async function createPromptTab(url: string): Promise<number> {
const tabOptions: browser.Tabs.CreateCreatePropertiesType = {
url: url,
};

const result = await browser.tabs.create(tabOptions);
return result.id!;
}

async function getPosition(
width: number,
height: number
): Promise<{ top: number; left: number }> {
Expand Down

0 comments on commit ec419a5

Please sign in to comment.