Skip to content

Commit

Permalink
Merge branch 'master' into feat/encrypt-prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
reneaaron committed Dec 17, 2023
2 parents d506289 + ec419a5 commit 50d495f
Show file tree
Hide file tree
Showing 29 changed files with 345 additions and 653 deletions.
100 changes: 61 additions & 39 deletions src/app/screens/connectors/ConnectGaloy/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ export const galoyUrls = {
label: "Blink Wallet",
website: "https://www.blink.sv/",
logo: galoyBlink,
url: process.env.BLINK_GALOY_URL || "https://api.mainnet.galoy.io/graphql",
url: process.env.BLINK_GALOY_URL || "https://api.blink.sv/graphql",
getHeaders: (authToken: string) => ({
Accept: "application/json",
"Content-Type": "application/json",
"X-API-KEY": authToken,
}),
apiCompatibilityMode: false,
},
"galoy-bitcoin-jungle": {
i18nPrefix: "bitcoin_jungle",
Expand All @@ -28,61 +34,61 @@ export const galoyUrls = {
url:
process.env.BITCOIN_JUNGLE_GALOY_URL ||
"https://api.mainnet.bitcoinjungle.app/graphql",
getHeaders: (authToken: string) => ({
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`,
}),
apiCompatibilityMode: true,
},
} as const;

const defaultHeaders = {
Accept: "application/json",
"Content-Type": "application/json",
};

type Props = {
instance: keyof typeof galoyUrls;
};

export default function ConnectGaloy(props: Props) {
const { instance } = props;
const { url, label, website, i18nPrefix, logo } = galoyUrls[instance];
const { url, label, website, i18nPrefix, logo, apiCompatibilityMode } =
galoyUrls[instance];

const navigate = useNavigate();
const { t } = useTranslation("translation", {
keyPrefix: "choose_connector",
});
const [loading, setLoading] = useState(false);
const [jwt, setJwt] = useState<string | undefined>();
const [authToken, setAuthToken] = useState<string | undefined>();

function handleJwtChange(event: React.ChangeEvent<HTMLInputElement>) {
setJwt(event.target.value.trim());
function handleAuthTokenChange(event: React.ChangeEvent<HTMLInputElement>) {
setAuthToken(event.target.value.trim());
}

async function loginWithJwt(event: React.FormEvent<HTMLFormElement>) {
async function loginWithAuthToken(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setLoading(true);
const meQuery = {
query: `
query getinfo {
me {
defaultAccount {
defaultWalletId
wallets {
walletCurrency
id
}
}
}
}
`,
};
try {
if (!jwt) {
if (!authToken) {
const errorMsg = `${t("galoy.errors.missing_token")}`;
throw new Error(errorMsg);
}
const authToken = jwt;

const headers = galoyUrls[instance].getHeaders(authToken);

const { data: meData } = await axios.post(url, meQuery, {
headers: {
...defaultHeaders,
Authorization: `Bearer ${authToken}`,
},
headers: headers,
adapter: fetchAdapter,
});

if (meData.error || meData.errors) {
const error = meData.error || meData.errors;
console.error(error);
Expand All @@ -92,8 +98,12 @@ export default function ConnectGaloy(props: Props) {
}`;
toast.error(alertMsg);
} else {
const walletId = meData.data.me.defaultAccount.defaultWalletId;
saveAccount({ authToken, walletId });
// Find the BTC wallet and get its ID
const btcWallet = meData.data.me.defaultAccount.wallets.find(
(w: Wallet) => w.walletCurrency === "BTC"
);
const walletId = btcWallet.id;
saveAccount({ headers, walletId });
}
} catch (e: unknown) {
console.error(e);
Expand All @@ -112,15 +122,16 @@ export default function ConnectGaloy(props: Props) {
}
}

async function saveAccount(config: { authToken: string; walletId: string }) {
async function saveAccount(config: { headers: Headers; walletId: string }) {
setLoading(true);

const account = {
name: label,
config: {
url,
accessToken: config.authToken,
headers: config.headers,
walletId: config.walletId,
apiCompatibilityMode,
},
connector: "galoy",
};
Expand Down Expand Up @@ -167,40 +178,38 @@ export default function ConnectGaloy(props: Props) {
logo={logo}
submitLabel={t("galoy.actions.login")}
submitLoading={loading}
onSubmit={loginWithJwt}
onSubmit={loginWithAuthToken}
description={
<Trans
i18nKey={"galoy.token.info"}
i18nKey={`${i18nPrefix}.token.info`}
t={t}
values={{ label }}
components={[
// eslint-disable-next-line react/jsx-key
<a
href="https://wallet.mainnet.galoy.io"
href="https://dashboard.blink.sv"
className="underline"
target="_blank"
rel="noopener noreferrer"
key="Blink Dashboard"
></a>,
// eslint-disable-next-line react/jsx-key
<br />,
// eslint-disable-next-line react/jsx-key
<b></b>,
]}
/>
}
>
{
<div className="mt-6">
<label
htmlFor="jwt"
htmlFor="authToken"
className="block font-medium text-gray-800 dark:text-white"
>
{t("galoy.token.label")}
{t(`${i18nPrefix}.token.label`)}
</label>
<div className="mt-1">
<Input
id="jwt"
name="jwt"
id="authToken"
name="authToken"
required
onChange={handleJwtChange}
onChange={handleAuthTokenChange}
autoFocus={true}
/>
</div>
Expand All @@ -209,3 +218,16 @@ export default function ConnectGaloy(props: Props) {
</ConnectorForm>
);
}

type Headers = {
[key: string]: string;
};

type Props = {
instance: keyof typeof galoyUrls;
};

type Wallet = {
walletCurrency: string;
id: string;
};
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
Loading

0 comments on commit 50d495f

Please sign in to comment.