chrome.identity.launchwebauthflow alternative

Hey! I wanted to open up a discussion on an alternative for chrome.identity.launchwebauthflow because the firefox version is buggy (https://github.com/mozilla/webextension-polyfill/issues/248) and Safari doesn't support it at all. I'm exploring a couple of options including just opening a new tab but wanted to see if anyone has experience here and can provide pointers. One of the pros of using a new tab/window is that if the user has Google Pay set up, it will work for stripe (unlike in launchwebauthflow) and if they have a password manager it will autofill logins for the user. Here is what I have so far (not much)
/* eslint-disable no-inner-declarations */
/* eslint-disable no-await-in-loop */
type LaunchWebAuthFlow = {
url: string;
};

export async function launchWebAuthFlow(props: LaunchWebAuthFlow) {
return new Promise((resolve, reject) => {
(async () => {
try {
const { url } = props;

const authWindow = await chrome.windows.create({
url,
type: 'popup',
});

chrome.windows.onRemoved.addListener((windowId) => {
if (windowId === authWindow.id) {
reject(new Error('user closed window'));
}
});

let counter = 0;
let done = false;

while (counter < 100 && !done) {
counter += 1;
if (authWindow.id === undefined) {
done = true;
break;
}

// eslint-disable-next-line no-promise-executor-return
await new Promise((res) => setTimeout(res, 1000));
const windowStatus = await chrome.windows.get(authWindow.id);
console.log('window status', windowStatus);
}
console.log(window);

// resolve(result);
} catch (err) {
reject(err);
}
})();
});
}
/* eslint-disable no-inner-declarations */
/* eslint-disable no-await-in-loop */
type LaunchWebAuthFlow = {
url: string;
};

export async function launchWebAuthFlow(props: LaunchWebAuthFlow) {
return new Promise((resolve, reject) => {
(async () => {
try {
const { url } = props;

const authWindow = await chrome.windows.create({
url,
type: 'popup',
});

chrome.windows.onRemoved.addListener((windowId) => {
if (windowId === authWindow.id) {
reject(new Error('user closed window'));
}
});

let counter = 0;
let done = false;

while (counter < 100 && !done) {
counter += 1;
if (authWindow.id === undefined) {
done = true;
break;
}

// eslint-disable-next-line no-promise-executor-return
await new Promise((res) => setTimeout(res, 1000));
const windowStatus = await chrome.windows.get(authWindow.id);
console.log('window status', windowStatus);
}
console.log(window);

// resolve(result);
} catch (err) {
reject(err);
}
})();
});
}
10 Replies
nahtnam
nahtnamOP•2y ago
In launchwebauthflow, the page auto closes. Thankfully since this window was opened programmatically, the final redirect page can call window.close() and it should work. I still need to set up the success case (I think I need to use this: https://developer.chrome.com/docs/extensions/mv3/messaging/#external-webpage) But open to feedback on this. I'll test on chrome, firefox (and safari eventually), if this works I'd suggest it be a plasmo library 🙂 Once I get the message passing working, I probably wont need any of the while loop stuff (just added that for testing) Notes: window.close() doesnt work It doesnt work in firefox because of lack of externally_connectable support Other than that, this is probably better in the long run than the identityApi because of autofill
lab
lab•2y ago
Can you control the popup windows via background script on FF or is that also doesnt' work? xd I was thinking if window.close() doesn't work you can send a message to your bgsw (or bgpages for ff) and tell it to close the popup
nahtnam
nahtnamOP•2y ago
This works, just wont work on firefox until it supports externally_connectable or im sure there is a suitable alternative (but might require more permissions) I'm probably going to generalize it more to my needs with some typescript generics BUT if plasmo wanted to release some sort of library to replace identity.launchWebAuthFlow this would be a pretty good starting point I think adding chrome.windows.remove(authWindow.id); seems to work too! Deleted my previous message, this is the final code I have that works for both stripe payment pages and todoist oauth. I'll test more but seems to cover the majority of my basic needs. In the extension:
export async function launchWebAuthFlow(props: LaunchWebAuthFlow) {
const { url } = props;

const authWindow = await chrome.windows.create({
url,
type: 'popup',
});

const result = new Promise<string | null>((resolve, reject) => {
function onMessageExternal(
request: any,
_sender: chrome.runtime.MessageSender,
sendResponse: (response?: any) => void,
) {
if (request.type === 'callback') {
sendResponse({ type: 'callback', success: true });
chrome.runtime.onMessageExternal.removeListener(onMessageExternal);
// if (authWindow.id) chrome.windows.remove(authWindow.id);
resolve(request.redirectUri);
}
}
chrome.runtime.onMessageExternal.addListener(onMessageExternal);

function onRemoved(windowId: number) {
if (windowId === authWindow.id) {
chrome.windows.onRemoved.removeListener(onRemoved);
reject(new Error('user closed window'));
}
}
chrome.windows.onRemoved.addListener(onRemoved);
});

return result;
}
export async function launchWebAuthFlow(props: LaunchWebAuthFlow) {
const { url } = props;

const authWindow = await chrome.windows.create({
url,
type: 'popup',
});

const result = new Promise<string | null>((resolve, reject) => {
function onMessageExternal(
request: any,
_sender: chrome.runtime.MessageSender,
sendResponse: (response?: any) => void,
) {
if (request.type === 'callback') {
sendResponse({ type: 'callback', success: true });
chrome.runtime.onMessageExternal.removeListener(onMessageExternal);
// if (authWindow.id) chrome.windows.remove(authWindow.id);
resolve(request.redirectUri);
}
}
chrome.runtime.onMessageExternal.addListener(onMessageExternal);

function onRemoved(windowId: number) {
if (windowId === authWindow.id) {
chrome.windows.onRemoved.removeListener(onRemoved);
reject(new Error('user closed window'));
}
}
chrome.windows.onRemoved.addListener(onRemoved);
});

return result;
}
on my website:
const sp = new URLSearchParams(searchParams?.toString())
const extId = sp.get("ext_id")
const redirectUri = sp.get("redirect_uri")

sp.delete("ext_id")
sp.delete("redirect_uri")

if (!redirectUri) {
setState({ state: "error", message: "We had trouble redirecting you back. Please try again later or contact support." })
return
}

const redirectUriWithProxy = new URL(redirectUri)
sp.forEach((value, key) => redirectUriWithProxy.searchParams.set(key, value))

try {
chrome.runtime.sendMessage(extId || env.NEXT_PUBLIC_CHROME_EXTENSION_ID, { type: "callback", redirectUri: redirectUriWithProxy.toString() }, (response) => {
if (response.type === "callback" && response.success) {
setState({ state: "success", message: "You may safely close this page." })
window.close()
return
}
setState({ state: "error", message: "There was a problem, please try again later or contact support." })
})
} catch (err) {
console.error(err)
setState({ state: "error", message: "There was a problem communicating, please try refreshing your new tab page." })
}
const sp = new URLSearchParams(searchParams?.toString())
const extId = sp.get("ext_id")
const redirectUri = sp.get("redirect_uri")

sp.delete("ext_id")
sp.delete("redirect_uri")

if (!redirectUri) {
setState({ state: "error", message: "We had trouble redirecting you back. Please try again later or contact support." })
return
}

const redirectUriWithProxy = new URL(redirectUri)
sp.forEach((value, key) => redirectUriWithProxy.searchParams.set(key, value))

try {
chrome.runtime.sendMessage(extId || env.NEXT_PUBLIC_CHROME_EXTENSION_ID, { type: "callback", redirectUri: redirectUriWithProxy.toString() }, (response) => {
if (response.type === "callback" && response.success) {
setState({ state: "success", message: "You may safely close this page." })
window.close()
return
}
setState({ state: "error", message: "There was a problem, please try again later or contact support." })
})
} catch (err) {
console.error(err)
setState({ state: "error", message: "There was a problem communicating, please try refreshing your new tab page." })
}
Hope this helps @louis Was thinking about using the @plasmohq/messaging library, but it looks like the docs are out of date. relay is deprecated. Do you also know if I can use sendToBackgroundViaRelay in a newtab page?
lab
lab•2y ago
You can use sendToBackground from newtab directly
nahtnam
nahtnamOP•2y ago
Would that allow website <-> newtab communication? Sounds like sendToBackground would allow newtab <> background communication
lab
lab•2y ago
oh yeah, I thought you wanted to send to bgsw. To comms between website <-> new tab, you will need to use relay hmm I wonder how that'd work - are you rendering this site inside an iframe? or something like that?
nahtnam
nahtnamOP•2y ago
Nope, see the code above, specifically the launchWebAuthFlow function
lab
lab•2y ago
oh like with
chrome.windows.create({
url,
type: 'popup',
});
chrome.windows.create({
url,
type: 'popup',
});
Is that where you're opening this "webpage"? What's the host of url? Is it the auth service itself, a webpage you own, or a tab/newtab page?
nahtnam
nahtnamOP•2y ago
Yep, I'm opening the page from the new tab page (on button click). The URL is stripe, however the redirect url is to a domain that I own which calls chrome.runtime.sendMessage (see other code block). The two code blocks above work together to act exactly like launchWebAuthFlow but works better because google pay and password managers work in the new window (it does not in launchWebAuthFlow)
lab
lab•2y ago
I see, I wonder if content-script are injected in that windows, but as long as there is a CS that's being injected in that redirected url on that windows you created then, the CS can hold the relay and your page can safely use sendToBGSWWithRelay (whatever the function is called lol) I'm not sure if chrome.windows.create will have CS injected to them like normal browser windows so you will need to test that out a bit - simply add a CS matching that URL, and console log and see if there's any sign of life Since the idea with relay is that you have a CS injected in the page that act as the relay so as long as a relay exists, message will flow
Want results from more Discord servers?
Add your server