Skip to content

Commit

Permalink
Merge pull request #528 from cosmology-tech/noah/expand-iframe-api
Browse files Browse the repository at this point in the history
improve iframe functionality
  • Loading branch information
pyramation authored Jan 7, 2025
2 parents 381ddc4 + 6072379 commit 6588b38
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 101 deletions.
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"@cosmjs/cosmwasm-stargate": "^0.32.3",
"@cosmjs/proto-signing": "^0.32.3",
"@cosmjs/stargate": "^0.32.3",
"@dao-dao/cosmiframe": "^0.1.0",
"@dao-dao/cosmiframe": "^1.0.0-rc.1",
"@walletconnect/types": "2.11.0",
"bowser": "2.11.0",
"cosmjs-types": "^0.9.0",
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/cosmiframe/cosmiframe.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Origin } from '@dao-dao/cosmiframe';

import { cosmiframeExtensionInfo, CosmiframeWallet } from './extension';

export const makeCosmiframeWallet = (allowedParentOrigins: string[]) =>
export const makeCosmiframeWallet = (allowedParentOrigins: Origin[]) =>
new CosmiframeWallet({
...cosmiframeExtensionInfo,
allowedParentOrigins,
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/cosmiframe/extension/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Origin } from '@dao-dao/cosmiframe';

import { Wallet } from '../../types';

export type CosmiframeWalletInfo = Wallet & {
allowedParentOrigins: string[];
allowedParentOrigins: Origin[];
};
14 changes: 10 additions & 4 deletions packages/core/src/manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { AssetList, Chain } from '@chain-registry/types';
import { isInIframe } from '@dao-dao/cosmiframe';
import { isInIframe, Origin } from '@dao-dao/cosmiframe';
import Bowser from 'bowser';
import EventEmitter from 'events';

Expand Down Expand Up @@ -52,7 +52,13 @@ export class WalletManager extends StateBase {
logger: Logger,
throwErrors: boolean | 'connect_only',
subscribeConnectEvents = true,
allowedCosmiframeParentOrigins?: string[],
allowedCosmiframeParentOrigins: Origin[] = [
/^https?:\/\/localhost(:\d+)?/,
/^https:\/\/(.+\.)?osmosis\.zone/,
/^https:\/\/(.+\.)?daodao\.zone/,
/^https:\/\/.+-da0da0\.vercel\.app/,
/^https:\/\/(.+\.)?abstract\.money/,
],
assetLists?: AssetList[],
defaultNameService?: NameServiceName,
walletConnectOptions?: WalletConnectOptions,
Expand Down Expand Up @@ -87,8 +93,8 @@ export class WalletManager extends StateBase {
];
wallets.forEach(
({ walletName }) =>
(this._reconnectMap[walletName] = () =>
this._reconnect(walletName, true))
(this._reconnectMap[walletName] = () =>
this._reconnect(walletName, true))
);
this.init(
chains,
Expand Down
2 changes: 1 addition & 1 deletion packages/react-lite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"dependencies": {
"@chain-registry/types": "^0.46.11",
"@cosmos-kit/core": "^2.15.0",
"@dao-dao/cosmiframe": "^0.1.0"
"@dao-dao/cosmiframe": "^1.0.0-rc.1"
},
"resolutions": {
"@types/react": "^18.2"
Expand Down
100 changes: 67 additions & 33 deletions packages/react-lite/src/hooks/useIframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@cosmos-kit/core';
import {
Cosmiframe,
Origin,
OverrideHandler,
ParentMetadata,
} from '@dao-dao/cosmiframe';
Expand All @@ -20,6 +21,12 @@ export type FunctionKeys<T> = {
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never;
}[keyof T];

export type UseIframeSignerOverrides = Partial<{
[K in keyof (OfflineAminoSigner & OfflineDirectSigner)]: (
...params: Parameters<(OfflineAminoSigner & OfflineDirectSigner)[K]>
) => OverrideHandler | Promise<OverrideHandler>;
}>;

export type UseIframeOptions = {
/**
* Optionally attempt to use a specific wallet. Otherwise get the first active
Expand Down Expand Up @@ -47,16 +54,34 @@ export type UseIframeOptions = {
* should handle the function. By default, if nothing is returned, an error
* will be thrown with the message "Handled by outer wallet."
*/
signerOverrides?: Partial<{
[K in keyof (OfflineAminoSigner & OfflineDirectSigner)]: (
...params: Parameters<(OfflineAminoSigner & OfflineDirectSigner)[K]>
) => OverrideHandler | Promise<OverrideHandler>;
}>;
signerOverrides?:
| UseIframeSignerOverrides
| ((chainId: string) => UseIframeSignerOverrides)
| ((chainId: string) => Promise<UseIframeSignerOverrides>);
/**
* Optionally only respond to requests from iframes of specific origin. If
* undefined or empty, all origins are allowed.
*/
origins?: string[];
origins?: Origin[];
};

export type UseIframeReturn = {
/**
* The main wallet, or undefined if the wallet is not connected.
*/
wallet: MainWalletBase | undefined;
/**
* A ref callback to set the iframe element.
*/
iframeRef: RefCallback<HTMLIFrameElement | null>;
/**
* The iframe element.
*/
iframe: HTMLIFrameElement | null;
/**
* A function to trigger a keystore change event on the iframe app wallet.
*/
triggerKeystoreChange: () => void;
};

export const useIframe = ({
Expand All @@ -65,10 +90,7 @@ export const useIframe = ({
walletClientOverrides,
signerOverrides,
origins,
}: UseIframeOptions = {}): {
wallet: MainWalletBase;
iframeRef: RefCallback<HTMLIFrameElement | null>;
} => {
}: UseIframeOptions = {}): UseIframeReturn => {
const wallet = useWallet(walletName).mainWallet;
const [iframe, setIframe] = useState<HTMLIFrameElement | null>(null);

Expand All @@ -79,54 +101,51 @@ export const useIframe = ({
const signerOverridesRef = useRef(signerOverrides);
signerOverridesRef.current = signerOverrides;

// Broadcast keystore change event to iframe wallet.
useEffect(() => {
const notifyIframe = () => {
const triggerKeystoreChange = useCallback(
() =>
iframe?.contentWindow.postMessage(
{
event: COSMIFRAME_KEYSTORECHANGE_EVENT,
},
'*'
);
};
),
[iframe]
);

// Broadcast keystore change event to iframe wallet.
useEffect(() => {
// Notify inner window of keystore change on any wallet client change
// (likely either connection or disconnection).
notifyIframe();
triggerKeystoreChange();

if (!wallet || typeof window === 'undefined') {
return;
}

// Notify inner window of keystore change on any wallet connect event.
wallet.walletInfo.connectEventNamesOnWindow?.forEach((eventName) => {
window.addEventListener(eventName, notifyIframe);
window.addEventListener(eventName, triggerKeystoreChange);
});
wallet.walletInfo.connectEventNamesOnClient?.forEach(async (eventName) => {
wallet.client?.on?.(eventName, notifyIframe);
wallet.client?.on?.(eventName, triggerKeystoreChange);
});

return () => {
wallet.walletInfo.connectEventNamesOnWindow?.forEach((eventName) => {
window.removeEventListener(eventName, notifyIframe);
window.removeEventListener(eventName, triggerKeystoreChange);
});
wallet.walletInfo.connectEventNamesOnClient?.forEach(
async (eventName) => {
wallet.client?.off?.(eventName, notifyIframe);
wallet.client?.off?.(eventName, triggerKeystoreChange);
}
);
};
}, [wallet, iframe]);
}, [wallet, triggerKeystoreChange]);

// Whenever wallet changes, broadcast keystore change event to iframe wallet.
useEffect(() => {
iframe?.contentWindow.postMessage(
{
event: COSMIFRAME_KEYSTORECHANGE_EVENT,
},
'*'
);
}, [wallet, iframe]);
triggerKeystoreChange();
}, [wallet, triggerKeystoreChange]);

useEffect(() => {
if (!iframe) {
Expand All @@ -138,13 +157,21 @@ export const useIframe = ({
target: wallet?.client || {},
getOfflineSignerDirect:
wallet?.client.getOfflineSignerDirect.bind(wallet.client) ||
(() => Promise.reject(COSMIFRAME_NOT_CONNECTED_MESSAGE)),
(() =>
Promise.reject(
COSMIFRAME_NOT_CONNECTED_MESSAGE +
' No direct signer client function found.'
)),
getOfflineSignerAmino:
wallet?.client.getOfflineSignerAmino.bind(wallet.client) ||
(() => Promise.reject(COSMIFRAME_NOT_CONNECTED_MESSAGE)),
(() =>
Promise.reject(
COSMIFRAME_NOT_CONNECTED_MESSAGE +
' No amino signer client function found.'
)),
nonSignerOverrides: () => ({
...walletClientOverridesRef.current,
// Override connect to return wallet info.
// Override connect to return specific error.
connect: async (...params) => {
if (walletClientOverridesRef.current?.connect) {
return await walletClientOverridesRef.current.connect(
Expand All @@ -156,12 +183,17 @@ export const useIframe = ({
} else {
return {
type: 'error',
error: COSMIFRAME_NOT_CONNECTED_MESSAGE,
error:
COSMIFRAME_NOT_CONNECTED_MESSAGE +
' No connect client function found or override provided.',
};
}
},
}),
signerOverrides: () => signerOverridesRef.current,
signerOverrides: async (chainId) =>
typeof signerOverridesRef.current === 'function'
? signerOverridesRef.current(chainId)
: signerOverridesRef.current,
origins,
metadata: {
name:
Expand Down Expand Up @@ -189,5 +221,7 @@ export const useIframe = ({
return {
wallet,
iframeRef,
iframe,
triggerKeystoreChange,
};
};
20 changes: 6 additions & 14 deletions packages/react-lite/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
WalletModalProps,
WalletRepo,
} from '@cosmos-kit/core';
import { Origin } from '@dao-dao/cosmiframe';
import { createContext, ReactNode, useEffect, useMemo, useState } from 'react';

export const walletContext = createContext<{
Expand All @@ -35,16 +36,7 @@ export function ChainProvider({
endpointOptions,
sessionOptions,
logLevel = 'WARN',
allowedIframeParentOrigins = [
'http://localhost:3000',
'https://localhost:3000',
'https://app.osmosis.zone',
'https://daodao.zone',
'https://dao.daodao.zone',
'https://my.abstract.money',
'https://apps.abstract.money',
'https://console.abstract.money',
],
allowedIframeParentOrigins,
children,
}: {
chains: (Chain | ChainName)[];
Expand All @@ -63,9 +55,9 @@ export function ChainProvider({
* Origins to allow wrapping this app in an iframe and connecting to this
* Cosmos Kit instance.
*
* Defaults to Osmosis and DAO DAO.
* Defaults to localhost, Osmosis, DAO DAO, and Abstract.
*/
allowedIframeParentOrigins?: string[];
allowedIframeParentOrigins?: Origin[];
children: ReactNode;
}) {
const logger = useMemo(() => new Logger(logLevel), []);
Expand Down Expand Up @@ -143,10 +135,10 @@ export function ChainProvider({
}, []);

useEffect(() => {
walletManager && walletManager.onMounted();
walletManager?.onMounted();
return () => {
setViewOpen(false);
walletManager && walletManager.onUnmounted();
walletManager?.onUnmounted();
};
}, [render, walletManager]);

Expand Down
3 changes: 2 additions & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@
},
"devDependencies": {
"@types/react": "^18.2",
"@types/react-dom": "^18.2"
"@types/react-dom": "^18.2",
"@dao-dao/cosmiframe": "^1.0.0-rc.1"
},
"dependencies": {
"@chain-registry/types": "^0.46.11",
Expand Down
18 changes: 5 additions & 13 deletions packages/react/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import {
WalletModalProps,
} from '@cosmos-kit/core';
import { ChainProvider as ChainProviderLite } from '@cosmos-kit/react-lite';
import { Origin } from '@dao-dao/cosmiframe';
import { ReactNode, useCallback, useMemo } from 'react';

import { SelectedWalletRepoProvider } from './context';
import { ThemeCustomizationProps, WalletModal } from './modal';
import { defaultModalViews } from './modal/components/views';
import { SelectedWalletRepoProvider } from './context';

export const ChainProvider = ({
chains,
Expand All @@ -33,16 +34,7 @@ export const ChainProvider = ({
endpointOptions,
sessionOptions,
logLevel = 'WARN',
allowedIframeParentOrigins = [
'http://localhost:3000',
'https://localhost:3000',
'https://app.osmosis.zone',
'https://daodao.zone',
'https://dao.daodao.zone',
'https://my.abstract.money',
'https://apps.abstract.money',
'https://console.abstract.money'
],
allowedIframeParentOrigins,
children,
modalTheme = {},
modalOptions,
Expand All @@ -64,9 +56,9 @@ export const ChainProvider = ({
* Origins to allow wrapping this app in an iframe and connecting to this
* Cosmos Kit instance.
*
* Defaults to Osmosis and DAO DAO.
* Defaults to Osmosis, DAO DAO, and Abstract.
*/
allowedIframeParentOrigins?: string[];
allowedIframeParentOrigins?: Origin[];
children: ReactNode;
modalTheme?: ThemeCustomizationProps;
modalOptions?: ModalOptions;
Expand Down
Loading

0 comments on commit 6588b38

Please sign in to comment.