diff --git a/examples/ssx-test-app/package.json b/examples/ssx-test-app/package.json index 880d5570..0c504909 100644 --- a/examples/ssx-test-app/package.json +++ b/examples/ssx-test-app/package.json @@ -24,6 +24,7 @@ "process": "^0.11.10", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.14.0", "react-scripts": "5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", diff --git a/examples/ssx-test-app/src/App.tsx b/examples/ssx-test-app/src/App.tsx index 631ee5df..7d536169 100644 --- a/examples/ssx-test-app/src/App.tsx +++ b/examples/ssx-test-app/src/App.tsx @@ -1,424 +1,19 @@ -import { useEffect, useState } from 'react'; -import { SSX } from '@spruceid/ssx'; -import Header from './components/Header'; -import Title from './components/Title'; -import Dropdown from './components/Dropdown'; -import RadioGroup from './components/RadioGroup'; -import Input from './components/Input'; -import Button from './components/Button'; -import AccountInfo from './components/AccountInfo'; -import { useWeb3Modal } from '@web3modal/react'; -import { getWalletClient } from '@wagmi/core' -import StorageModule from './components/StorageModule'; -import { walletClientToEthers5Signer } from './utils/web3modalV2Settings'; -import { useWalletClient } from 'wagmi'; -import './App.css'; +import React from 'react'; +import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; -declare global { - interface Window { - ssx: SSX; - } -} +import Home from './pages/Home'; +import Shared from './pages/Shared'; +import './App.css'; function App() { - - const { open: openWeb3Modal } = useWeb3Modal(); - const { data: walletClient } = useWalletClient() - - const [loading, setLoading] = useState(false); - - const [ssx, setSSX] = useState(null); - const [provider, setProvider] = useState('MetaMask'); - const [enableDaoLogin, setDaoLogin] = useState('Off'); - const [server, setServer] = useState('Off'); - const [resolveEns, setResolveEns] = useState('Off'); - const [resolveLens, setResolveLens] = useState('Off'); - const [siweConfig, setSiweConfig] = useState('Off'); - const [host, setHost] = useState(''); - const [resolveOnServer, setResolveOnServer] = useState('Off'); - const [resolveEnsDomain, setResolveEnsDomain] = useState('On'); - const [resolveEnsAvatar, setResolveEnsAvatar] = useState('On'); - // siweConfig Fields - const [address, setAddress] = useState(''); - const [chainId, setChainId] = useState(''); - const [domain, setDomain] = useState(''); - const [nonce, setNonce] = useState(''); - const [issuedAt, setIssuedAt] = useState(''); - const [expirationTime, setExpirationTime] = useState(''); - const [requestId, setRequestId] = useState(''); - const [notBefore, setNotBefore] = useState(''); - const [resources, setResources] = useState(''); - const [statement, setStatement] = useState(''); - // ssx module config - const [storageEnabled, setStorageEnabled] = useState('Off'); - - const getSSXConfig = (ssxConfig: Record = {}) => { - if (server === 'On') { - ssxConfig = { - providers: { - ...ssxConfig?.provider, - server: { - host - }, - } - } - } - - if (siweConfig === 'On') { - const siweConfig: Record = {}; - if (address) siweConfig.address = address; - if (chainId) siweConfig.chainId = chainId; - if (domain) siweConfig.domain = domain; - if (nonce) siweConfig.nonce = nonce; - if (issuedAt) siweConfig.issuedAt = issuedAt; - if (expirationTime) siweConfig.expirationTime = expirationTime; - if (requestId) siweConfig.requestId = requestId; - if (notBefore) siweConfig.notBefore = notBefore; - if (resources) siweConfig.resources = resources.split(',').map(r => r.trim()); - if (statement) siweConfig.statement = statement; - ssxConfig = { - ...ssxConfig, - ...(siweConfig && { siweConfig }) - } - } - - ssxConfig = { - ...ssxConfig, - enableDaoLogin: enableDaoLogin === 'On' - } - - if (resolveEns === 'On') { - ssxConfig = { - ...ssxConfig, - resolveEns: { - resolveOnServer: resolveOnServer === 'On', - resolve: { - domain: resolveEnsDomain === 'On', - avatar: resolveEnsAvatar === 'On' - } - } - } - } - - if (resolveLens === 'On' || resolveLens === 'onServer') { - ssxConfig = { - ...ssxConfig, - resolveLens: resolveLens === 'On' ? true : resolveLens - } - } - - const modules: Record = {}; - - if (storageEnabled === "On") { - modules.storage = true; - } - - if (modules) { - ssxConfig = { - ...ssxConfig, - modules - } - } - - return ssxConfig; - }; - - const signInUsingWeb3Modal = async (walletClient: any) => { - const chainId = await walletClient.getChainId(); - const newWalletClient = await getWalletClient({ chainId }); - const signer = walletClientToEthers5Signer(newWalletClient as any); - if (ssx) return; - - setLoading(true); - const ssxConfig = getSSXConfig({ - provider: { - web3: { - driver: signer.provider - } - } - }); - - const ssxProvider = new SSX(ssxConfig); - - try { - await ssxProvider.signIn(); - setSSX(ssxProvider); - } catch (err) { - console.error(err); - } - setLoading(false); - } - - useEffect(() => { - if (walletClient) { - signInUsingWeb3Modal(walletClient); - } else { - ssx?.signOut?.(); - setSSX(null); - } - // eslint-disable-next-line - }, [walletClient]); - - const ssxHandler = async () => { - if (provider === 'Web3Modal v2') { - return openWeb3Modal(); - } else { - setLoading(true); - let ssxConfig = getSSXConfig(); - - const ssx = new SSX(ssxConfig); - window.ssx = ssx; - - try { - await ssx.signIn(); - setSSX(ssx); - } catch (err) { - console.error(err); - } - setLoading(false); - } - }; - - const ssxLogoutHandler = async () => { - if (provider === 'Web3Modal v2') { - return openWeb3Modal(); - } - - ssx?.signOut?.(); - setSSX(null); - }; - return ( -
- -
- - <div className='Content'> - <div className='Content-container'> - { - ssx ? - <> - <Button - id='signOutButton' - onClick={ssxLogoutHandler} - loading={loading} - > - SIGN-OUT - </Button> - <AccountInfo - address={ssx?.address()} - session={ssx?.session()} - /> - </> : - <> - <Button - id='signInButton' - onClick={ssxHandler} - loading={loading} - > - SIGN-IN WITH ETHEREUM - </Button> - </> - } - <Dropdown - id='selectPreferences' - label='Select Preference(s)' - > - <div className='Dropdown-item'> - <span className='Dropdown-item-name'> - Provider - </span> - <div className='Dropdown-item-options'> - <RadioGroup - name='provider' - options={['MetaMask', 'Web3Modal v2']} - value={provider} - onChange={setProvider} - inline={false} - /> - </div> - </div> - <div className='Dropdown-item'> - <span className='Dropdown-item-name'> - daoLogin - </span> - <div className='Dropdown-item-options'> - <RadioGroup - name='enableDaoLogin' - options={['On', 'Off']} - value={enableDaoLogin} - onChange={setDaoLogin} - /> - </div> - </div> - <div className='Dropdown-item'> - <span className='Dropdown-item-name'> - Server - </span> - <div className='Dropdown-item-options'> - <RadioGroup - name='server' - options={['On', 'Off']} - value={server} - onChange={setServer} - /> - </div> - </div> - <div className='Dropdown-item'> - <span className='Dropdown-item-name'> - resolveEns - </span> - <div className='Dropdown-item-options'> - <RadioGroup - name='resolveEns' - options={['On', 'Off']} - value={resolveEns} - onChange={setResolveEns} - /> - </div> - </div> - <div className='Dropdown-item'> - <span className='Dropdown-item-name'> - resolveLens - </span> - <div className='Dropdown-item-options'> - <RadioGroup - name='resolveLens' - options={['On', 'Off', 'onServer']} - value={resolveLens} - onChange={setResolveLens} - /> - </div> - </div> - <div className='Dropdown-item'> - <span className='Dropdown-item-name'> - siweConfig - </span> - <div className='Dropdown-item-options'> - <RadioGroup - name='siweConfig' - options={['On', 'Off']} - value={siweConfig} - onChange={setSiweConfig} - /> - </div> - </div> - <div className='Dropdown-item'> - <span className='Dropdown-item-name'> - Storage - </span> - <div className='Dropdown-item-options'> - <RadioGroup - name='storageEnabled' - options={['On', 'Off']} - value={storageEnabled} - onChange={setStorageEnabled} - /> - </div> - </div> - </Dropdown> - { - server === 'On' ? - <Input - label='Host' - value={host} - onChange={setHost} - /> : - null - } - { - resolveEns === 'On' ? - <> - <RadioGroup - label='Resolve ENS on Server' - name='resolveOnServer' - options={['On', 'Off']} - value={resolveOnServer} - onChange={setResolveOnServer} - /> - <RadioGroup - label='Resolve ENS Domain' - name='resolveEnsDomain' - options={['On', 'Off']} - value={resolveEnsDomain} - onChange={setResolveEnsDomain} - /> - <RadioGroup - label='Resolve ENS Avatar' - name='resolveEnsAvatar' - options={['On', 'Off']} - value={resolveEnsAvatar} - onChange={setResolveEnsAvatar} - /> - </> : - null - } - { - siweConfig === 'On' ? - <div> - <Input - label='Address' - value={address} - onChange={setAddress} - /> - <Input - label='Chain ID' - value={chainId} - onChange={setChainId} - /> - <Input - label='Domain' - value={domain} - onChange={setDomain} - /> - <Input - label='Nonce' - value={nonce} - onChange={setNonce} - /> - <Input - label='Issued At' - value={issuedAt} - onChange={setIssuedAt} - /> - <Input - label='Expiration Time' - value={expirationTime} - onChange={setExpirationTime} - /> - <Input - label='Request ID' - value={requestId} - onChange={setRequestId} - /> - <Input - label='Not Before' - value={notBefore} - onChange={setNotBefore} - /> - <Input - label='Resources' - value={resources} - onChange={setResources} - /> - <Input - label='Statement' - value={statement} - onChange={setStatement} - /> - </div> : - null - } - </div> - { - storageEnabled === "On" - && ssx - && <StorageModule ssx={ssx} /> - } - </div> - - </div> + <Router> + <Routes> + <Route path="/" element={<Home />} /> + <Route path="/share" element={<Shared />} /> + </Routes> + </Router> ); } -export default App; \ No newline at end of file +export default App; diff --git a/examples/ssx-test-app/src/pages/Home.tsx b/examples/ssx-test-app/src/pages/Home.tsx new file mode 100644 index 00000000..5a56d3a3 --- /dev/null +++ b/examples/ssx-test-app/src/pages/Home.tsx @@ -0,0 +1,423 @@ +import { useEffect, useState } from 'react'; +import { SSX } from '@spruceid/ssx'; +import Header from '../components/Header'; +import Title from '../components/Title'; +import Dropdown from '../components/Dropdown'; +import RadioGroup from '../components/RadioGroup'; +import Input from '../components/Input'; +import Button from '../components/Button'; +import AccountInfo from '../components/AccountInfo'; +import { useWeb3Modal } from '@web3modal/react'; +import { getWalletClient } from '@wagmi/core' +import StorageModule from '../pages/StorageModule'; +import { walletClientToEthers5Signer } from '../utils/web3modalV2Settings'; +import { useWalletClient } from 'wagmi'; + +declare global { + interface Window { + ssx: SSX; + } +} + +function Home() { + + const { open: openWeb3Modal } = useWeb3Modal(); + const { data: walletClient } = useWalletClient() + + const [loading, setLoading] = useState(false); + + const [ssx, setSSX] = useState<SSX | null>(null); + const [provider, setProvider] = useState<string>('MetaMask'); + const [enableDaoLogin, setDaoLogin] = useState<string>('Off'); + const [server, setServer] = useState<string>('Off'); + const [resolveEns, setResolveEns] = useState<string>('Off'); + const [resolveLens, setResolveLens] = useState<string>('Off'); + const [siweConfig, setSiweConfig] = useState<string>('Off'); + const [host, setHost] = useState<string>(''); + const [resolveOnServer, setResolveOnServer] = useState<string>('Off'); + const [resolveEnsDomain, setResolveEnsDomain] = useState<string>('On'); + const [resolveEnsAvatar, setResolveEnsAvatar] = useState<string>('On'); + // siweConfig Fields + const [address, setAddress] = useState<string>(''); + const [chainId, setChainId] = useState<string>(''); + const [domain, setDomain] = useState<string>(''); + const [nonce, setNonce] = useState<string>(''); + const [issuedAt, setIssuedAt] = useState<string>(''); + const [expirationTime, setExpirationTime] = useState<string>(''); + const [requestId, setRequestId] = useState<string>(''); + const [notBefore, setNotBefore] = useState<string>(''); + const [resources, setResources] = useState<string>(''); + const [statement, setStatement] = useState<string>(''); + // ssx module config + const [storageEnabled, setStorageEnabled] = useState<string>('Off'); + + const getSSXConfig = (ssxConfig: Record<string, any> = {}) => { + if (server === 'On') { + ssxConfig = { + providers: { + ...ssxConfig?.provider, + server: { + host + }, + } + } + } + + if (siweConfig === 'On') { + const siweConfig: Record<string, any> = {}; + if (address) siweConfig.address = address; + if (chainId) siweConfig.chainId = chainId; + if (domain) siweConfig.domain = domain; + if (nonce) siweConfig.nonce = nonce; + if (issuedAt) siweConfig.issuedAt = issuedAt; + if (expirationTime) siweConfig.expirationTime = expirationTime; + if (requestId) siweConfig.requestId = requestId; + if (notBefore) siweConfig.notBefore = notBefore; + if (resources) siweConfig.resources = resources.split(',').map(r => r.trim()); + if (statement) siweConfig.statement = statement; + ssxConfig = { + ...ssxConfig, + ...(siweConfig && { siweConfig }) + } + } + + ssxConfig = { + ...ssxConfig, + enableDaoLogin: enableDaoLogin === 'On' + } + + if (resolveEns === 'On') { + ssxConfig = { + ...ssxConfig, + resolveEns: { + resolveOnServer: resolveOnServer === 'On', + resolve: { + domain: resolveEnsDomain === 'On', + avatar: resolveEnsAvatar === 'On' + } + } + } + } + + if (resolveLens === 'On' || resolveLens === 'onServer') { + ssxConfig = { + ...ssxConfig, + resolveLens: resolveLens === 'On' ? true : resolveLens + } + } + + const modules: Record<string, any> = {}; + + if (storageEnabled === "On") { + modules.storage = true; + } + + if (modules) { + ssxConfig = { + ...ssxConfig, + modules + } + } + + return ssxConfig; + }; + + const signInUsingWeb3Modal = async (walletClient: any) => { + const chainId = await walletClient.getChainId(); + const newWalletClient = await getWalletClient({ chainId }); + const signer = walletClientToEthers5Signer(newWalletClient as any); + if (ssx) return; + + setLoading(true); + const ssxConfig = getSSXConfig({ + provider: { + web3: { + driver: signer.provider + } + } + }); + + const ssxProvider = new SSX(ssxConfig); + + try { + await ssxProvider.signIn(); + setSSX(ssxProvider); + } catch (err) { + console.error(err); + } + setLoading(false); + } + + useEffect(() => { + if (walletClient) { + signInUsingWeb3Modal(walletClient); + } else { + ssx?.signOut?.(); + setSSX(null); + } + // eslint-disable-next-line + }, [walletClient]); + + const ssxHandler = async () => { + if (provider === 'Web3Modal v2') { + return openWeb3Modal(); + } else { + setLoading(true); + let ssxConfig = getSSXConfig(); + + const ssx = new SSX(ssxConfig); + window.ssx = ssx; + + try { + await ssx.signIn(); + setSSX(ssx); + } catch (err) { + console.error(err); + } + setLoading(false); + } + }; + + const ssxLogoutHandler = async () => { + if (provider === 'Web3Modal v2') { + return openWeb3Modal(); + } + + ssx?.signOut?.(); + setSSX(null); + }; + + return ( + <div className='App'> + + <Header /> + <Title /> + <div className='Content'> + <div className='Content-container'> + { + ssx ? + <> + <Button + id='signOutButton' + onClick={ssxLogoutHandler} + loading={loading} + > + SIGN-OUT + </Button> + <AccountInfo + address={ssx?.address()} + session={ssx?.session()} + /> + </> : + <> + <Button + id='signInButton' + onClick={ssxHandler} + loading={loading} + > + SIGN-IN WITH ETHEREUM + </Button> + </> + } + <Dropdown + id='selectPreferences' + label='Select Preference(s)' + > + <div className='Dropdown-item'> + <span className='Dropdown-item-name'> + Provider + </span> + <div className='Dropdown-item-options'> + <RadioGroup + name='provider' + options={['MetaMask', 'Web3Modal v2']} + value={provider} + onChange={setProvider} + inline={false} + /> + </div> + </div> + <div className='Dropdown-item'> + <span className='Dropdown-item-name'> + daoLogin + </span> + <div className='Dropdown-item-options'> + <RadioGroup + name='enableDaoLogin' + options={['On', 'Off']} + value={enableDaoLogin} + onChange={setDaoLogin} + /> + </div> + </div> + <div className='Dropdown-item'> + <span className='Dropdown-item-name'> + Server + </span> + <div className='Dropdown-item-options'> + <RadioGroup + name='server' + options={['On', 'Off']} + value={server} + onChange={setServer} + /> + </div> + </div> + <div className='Dropdown-item'> + <span className='Dropdown-item-name'> + resolveEns + </span> + <div className='Dropdown-item-options'> + <RadioGroup + name='resolveEns' + options={['On', 'Off']} + value={resolveEns} + onChange={setResolveEns} + /> + </div> + </div> + <div className='Dropdown-item'> + <span className='Dropdown-item-name'> + resolveLens + </span> + <div className='Dropdown-item-options'> + <RadioGroup + name='resolveLens' + options={['On', 'Off', 'onServer']} + value={resolveLens} + onChange={setResolveLens} + /> + </div> + </div> + <div className='Dropdown-item'> + <span className='Dropdown-item-name'> + siweConfig + </span> + <div className='Dropdown-item-options'> + <RadioGroup + name='siweConfig' + options={['On', 'Off']} + value={siweConfig} + onChange={setSiweConfig} + /> + </div> + </div> + <div className='Dropdown-item'> + <span className='Dropdown-item-name'> + Storage + </span> + <div className='Dropdown-item-options'> + <RadioGroup + name='storageEnabled' + options={['On', 'Off']} + value={storageEnabled} + onChange={setStorageEnabled} + /> + </div> + </div> + </Dropdown> + { + server === 'On' ? + <Input + label='Host' + value={host} + onChange={setHost} + /> : + null + } + { + resolveEns === 'On' ? + <> + <RadioGroup + label='Resolve ENS on Server' + name='resolveOnServer' + options={['On', 'Off']} + value={resolveOnServer} + onChange={setResolveOnServer} + /> + <RadioGroup + label='Resolve ENS Domain' + name='resolveEnsDomain' + options={['On', 'Off']} + value={resolveEnsDomain} + onChange={setResolveEnsDomain} + /> + <RadioGroup + label='Resolve ENS Avatar' + name='resolveEnsAvatar' + options={['On', 'Off']} + value={resolveEnsAvatar} + onChange={setResolveEnsAvatar} + /> + </> : + null + } + { + siweConfig === 'On' ? + <div> + <Input + label='Address' + value={address} + onChange={setAddress} + /> + <Input + label='Chain ID' + value={chainId} + onChange={setChainId} + /> + <Input + label='Domain' + value={domain} + onChange={setDomain} + /> + <Input + label='Nonce' + value={nonce} + onChange={setNonce} + /> + <Input + label='Issued At' + value={issuedAt} + onChange={setIssuedAt} + /> + <Input + label='Expiration Time' + value={expirationTime} + onChange={setExpirationTime} + /> + <Input + label='Request ID' + value={requestId} + onChange={setRequestId} + /> + <Input + label='Not Before' + value={notBefore} + onChange={setNotBefore} + /> + <Input + label='Resources' + value={resources} + onChange={setResources} + /> + <Input + label='Statement' + value={statement} + onChange={setStatement} + /> + </div> : + null + } + </div> + { + storageEnabled === "On" + && ssx + && <StorageModule ssx={ssx} /> + } + </div> + + </div> + ); +} + +export default Home; \ No newline at end of file diff --git a/examples/ssx-test-app/src/pages/Shared.tsx b/examples/ssx-test-app/src/pages/Shared.tsx new file mode 100644 index 00000000..1425e7b4 --- /dev/null +++ b/examples/ssx-test-app/src/pages/Shared.tsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { SSX } from '@spruceid/ssx'; +import Header from '../components/Header'; +import Button from '../components/Button'; +import Title from '../components/Title'; +import Input from '../components/Input'; + +const Shared = () => { + const location = useLocation(); + const queryParams = new URLSearchParams(location.search); + + const [shareData, setShareData] = useState(queryParams.get('data') || ""); + const [fetchedData, setFetchedData]: [any, any] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const fetchShareData = async () => { + setIsLoading(true); + const ssx = new SSX({ modules: { storage: true } }); + const data = await ssx.storage.retrieveSharingLink(shareData); + setFetchedData(data); + setIsLoading(false); + }; + + return ( + <div className="App"> + <Header /> + <Title /> + <div className="Content"> + <div className="Content-container"> + <Input + label="Share Data" + value={shareData} + onChange={(e: any) => setShareData(e.target.value)} + /> + <Button + id="fetchShareData" + onClick={fetchShareData} + loading={isLoading} + > + Fetch Share Data + </Button> + {fetchedData && ( + <div className="Output"> + <pre>{JSON.stringify(fetchedData, null, 2)}</pre> + </div> + )} + </div> + </div> + </div> + ); +}; + +export default Shared; \ No newline at end of file diff --git a/examples/ssx-test-app/src/components/StorageModule.tsx b/examples/ssx-test-app/src/pages/StorageModule.tsx similarity index 58% rename from examples/ssx-test-app/src/components/StorageModule.tsx rename to examples/ssx-test-app/src/pages/StorageModule.tsx index accebb01..69c8496d 100644 --- a/examples/ssx-test-app/src/components/StorageModule.tsx +++ b/examples/ssx-test-app/src/pages/StorageModule.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; -import Input from './Input'; -import Button from './Button' +import Input from '../components/Input'; +import Button from '../components/Button'; import { SSX } from '@spruceid/ssx'; interface IStorageModule { @@ -16,16 +16,26 @@ function StorageModule({ ssx }: IStorageModule) { useEffect(() => { const getContentList = async () => { - const { data } = await ssx.storage.list(); - setContentList(data.map((d: string) => { - const contentArr = d.split('/'); - contentArr.shift(); // remove the prefix - return contentArr.join('/'); - })); + const { data } = await ssx.storage.list({ removePrefix: true }); + setContentList( + data.map((d: string) => { + const contentArr = d.split('/'); + contentArr.shift(); // remove the prefix + return contentArr.join('/'); + }) + ); }; getContentList(); }, [ssx]); + const handleShareContent = async (content: string) => { + const prefix = ssx.storage.prefix; + const base64Content = await ssx.storage.generateSharingLink( + `${prefix}/${content}` + ); + const sharingLink = `${window.location.origin}/share?data=${base64Content}`; + await navigator.clipboard.writeText(sharingLink); + }; const handleGetContent = async (content: string) => { const { data } = await ssx.storage.get(content); @@ -37,7 +47,7 @@ function StorageModule({ ssx }: IStorageModule) { const handleDeleteContent = async (content: string) => { await ssx.storage.delete(content); - setContentList((prevList) => prevList.filter((c) => c !== content)); + setContentList(prevList => prevList.filter(c => c !== content)); setSelectedContent(null); setName(''); setText(''); @@ -51,12 +61,12 @@ function StorageModule({ ssx }: IStorageModule) { } await ssx.storage.put(name, text); if (selectedContent) { - setContentList((prevList) => - prevList.map((c) => (c === selectedContent ? name : c)) + setContentList(prevList => + prevList.map(c => (c === selectedContent ? name : c)) ); setSelectedContent(null); } else { - setContentList((prevList) => [...prevList, name]); + setContentList(prevList => [...prevList, name]); } setName(''); setText(''); @@ -77,13 +87,24 @@ function StorageModule({ ssx }: IStorageModule) { {viewingList ? ( <div className="List-pane"> <h3>List Pane</h3> - {contentList.map((content) => ( + {contentList.map(content => ( <div className="item-container" key={content}> <span>{content}</span> - <Button className="smallButton" onClick={() => handleGetContent(content)}>Get</Button> - <Button className="smallButton" onClick={() => handleDeleteContent(content)}> + <Button + className="smallButton" + onClick={() => handleGetContent(content)}> + Get + </Button> + <Button + className="smallButton" + onClick={() => handleDeleteContent(content)}> Delete </Button> + <Button + className="smallButton" + onClick={() => handleShareContent(content)}> + Share + </Button> </div> ))} <Button onClick={handlePostNewContent}>Post new content</Button> @@ -91,16 +112,8 @@ function StorageModule({ ssx }: IStorageModule) { ) : ( <div className="View-pane"> <h3>View/Edit/Post Pane</h3> - <Input - label="Key" - value={name} - onChange={setName} - /> - <Input - label="Text" - value={text} - onChange={setText} - /> + <Input label="Key" value={name} onChange={setName} /> + <Input label="Text" value={text} onChange={setText} /> <Button onClick={handlePostContent}>Post</Button> <Button onClick={() => setViewingList(true)}>Back</Button> </div> @@ -110,4 +123,4 @@ function StorageModule({ ssx }: IStorageModule) { ); } -export default StorageModule; \ No newline at end of file +export default StorageModule; diff --git a/packages/ssx-core/src/client/gnosis-extension-modal.ts b/packages/ssx-core/src/client/gnosis-extension-modal.ts index 1d122e59..8effd620 100644 --- a/packages/ssx-core/src/client/gnosis-extension-modal.ts +++ b/packages/ssx-core/src/client/gnosis-extension-modal.ts @@ -1,8 +1,4 @@ -import { - ConfigOverrides, - ISSXConnected, - SSXExtension, -} from './types'; +import { ConfigOverrides, ISSXConnected, SSXExtension } from './types'; import { providers } from 'ethers'; import { gnosisDelegatorsFor } from '../utils'; @@ -149,7 +145,8 @@ const getBaseModal = (): Element => { // Backdrop const backdrop = document.createElement('span'); backdrop.classList.add('ssx-gnosis-modal--backdrop'); - backdrop.onclick = () => window.gnosisModal.abortOperation('Operation aborted by the user.'); + backdrop.onclick = () => + window.gnosisModal.abortOperation('Operation aborted by the user.'); container.appendChild(backdrop); // Brand @@ -197,13 +194,14 @@ const getBaseModal = (): Element => { gLogo.appendChild(pathLogo3); svgLogo.appendChild(gLogo); const divSafe = document.createElement('div'); - divSafe.classList.add('ssx-gnosis-modal--header-title') + divSafe.classList.add('ssx-gnosis-modal--header-title'); divSafe.appendChild(document.createTextNode('Safe')); brand.appendChild(svgLogo); brand.appendChild(divSafe); // Close button const closeBtn = document.createElement('button'); - closeBtn.onclick = () => window.gnosisModal.abortOperation('Operation aborted by the user.'); + closeBtn.onclick = () => + window.gnosisModal.abortOperation('Operation aborted by the user.'); const svgClose = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' @@ -244,7 +242,8 @@ const getBaseModal = (): Element => { // Close button const footerCloseBtn = document.createElement('button'); footerCloseBtn.classList.add('ssx-gnosis-modal--btn', 'secondary'); - footerCloseBtn.onclick = () => window.gnosisModal.abortOperation('Operation aborted by the user.'); + footerCloseBtn.onclick = () => + window.gnosisModal.abortOperation('Operation aborted by the user.'); footerCloseBtn.appendChild(document.createTextNode('Cancel')); // Continue button const continueBtn = document.createElement('button'); @@ -350,8 +349,9 @@ export class GnosisDelegation implements SSXExtension { modalBodyContent.appendChild(newOption); }); modalBody.replaceChildren(modalBodyContent); - modalCounter.textContent = `${options.length} result${options.length > 1 ? 's' : '' - }`; + modalCounter.textContent = `${options.length} result${ + options.length > 1 ? 's' : '' + }`; modal.classList.add('visible'); }) .catch(e => { @@ -369,8 +369,9 @@ export class GnosisDelegation implements SSXExtension { modalBodyContent.appendChild(newOption); }); modalBody.replaceChildren(modalBodyContent); - modalCounter.textContent = `${options.length} result${options.length > 1 ? 's' : '' - }`; + modalCounter.textContent = `${options.length} result${ + options.length > 1 ? 's' : '' + }`; if (!modal.classList.contains('visible')) { modal.classList.add('visible'); } @@ -465,7 +466,10 @@ export class GnosisDelegation implements SSXExtension { */ connect = async (): Promise<void> => { const option = this.selectedOption.replace(/Yourself - /, ''); - const hasDelegator = [...delegators, this._connectedAddress].filter(delegator => option === delegator).length !== 1; + const hasDelegator = + [...delegators, this._connectedAddress].filter( + delegator => option === delegator + ).length !== 1; if (!option || hasDelegator) { this._failure(new Error('Invalid address selected.')); this.closeModal(); diff --git a/packages/ssx-core/src/client/types.ts b/packages/ssx-core/src/client/types.ts index 5e1c058f..1552d07a 100644 --- a/packages/ssx-core/src/client/types.ts +++ b/packages/ssx-core/src/client/types.ts @@ -75,7 +75,7 @@ export interface SSXClientProviders { } /** Optional session configuration for the SIWE message. */ -export interface SiweConfig extends Partial<ssxSession.SiweConfig> { } +export interface SiweConfig extends Partial<ssxSession.SiweConfig> {} /** Extra SIWE fields. */ export type ExtraFields = ssxSession.ExtraFields; @@ -95,7 +95,7 @@ export interface SSXEnsConfig { /** Interface to an intermediate SSX state: connected, but not signed-in. */ export interface ISSXConnected { - /** Instance of SSXSessionBuilder. */ + /** Instance of SSXSessionManager. */ builder: ssxSession.SSXSessionManager; /** SSXConfig object. */ config: SSXClientConfig; diff --git a/packages/ssx-core/src/types.ts b/packages/ssx-core/src/types.ts index 10d5757b..927fc4d2 100644 --- a/packages/ssx-core/src/types.ts +++ b/packages/ssx-core/src/types.ts @@ -28,20 +28,25 @@ export interface SSXRouteConfig { /** Type-Guard for SSXRouteConfig. */ export const isSSXRouteConfig = ( config: SSXServerRouteEndpointType -): config is SSXRouteConfig | AxiosRequestConfig | SSXServerMiddlewareConfig => typeof config === 'object'; - +): config is SSXRouteConfig | AxiosRequestConfig | SSXServerMiddlewareConfig => + typeof config === 'object'; export interface SSXServerMiddlewareConfig { path: string; callback?: (req: any, body?: Record<string, any>) => Promise<void> | void; -}; +} /** Type-Guard for SSXServerMiddlewareConfig. */ export const isSSXServerMiddlewareConfig = ( config: SSXServerRouteEndpointType -): config is SSXServerMiddlewareConfig => (config as SSXServerMiddlewareConfig)?.path !== undefined; +): config is SSXServerMiddlewareConfig => + (config as SSXServerMiddlewareConfig)?.path !== undefined; -export type SSXServerRouteEndpointType = Partial<SSXRouteConfig> | AxiosRequestConfig | string | SSXServerMiddlewareConfig; +export type SSXServerRouteEndpointType = + | Partial<SSXRouteConfig> + | AxiosRequestConfig + | string + | SSXServerMiddlewareConfig; /** Server endpoints configuration. */ export interface SSXServerRoutes { @@ -292,4 +297,4 @@ export interface SSXLensProfilesResponse { items: Array<SSXLensProfileData>; /** Lens pagination info. */ pageInfo?: SSXLensProfilesPageInfo; -} \ No newline at end of file +} diff --git a/packages/ssx-core/src/utils/queries.ts b/packages/ssx-core/src/utils/queries.ts index e548d822..c95301f6 100644 --- a/packages/ssx-core/src/utils/queries.ts +++ b/packages/ssx-core/src/utils/queries.ts @@ -88,4 +88,4 @@ export const getProfilesQuery = ` } } } -`; \ No newline at end of file +`; diff --git a/packages/ssx-core/src/utils/utils.ts b/packages/ssx-core/src/utils/utils.ts index ddc8071f..f1063400 100644 --- a/packages/ssx-core/src/utils/utils.ts +++ b/packages/ssx-core/src/utils/utils.ts @@ -85,9 +85,9 @@ export const ssxResolveEns = async ( /* Enables ENS avatar resolution */ avatar?: boolean; } = { - domain: true, - avatar: true, - } + domain: true, + avatar: true, + } ): Promise<SSXEnsData> => { if (!address) { throw new Error('Missing address.'); @@ -119,20 +119,20 @@ export const ssxResolveEns = async ( }; const LENS_API_LINKS = { - 'matic': 'https://api.lens.dev', - 'maticmum': 'https://api-mumbai.lens.dev' -} + matic: 'https://api.lens.dev', + maticmum: 'https://api-mumbai.lens.dev', +}; /** - * Resolves Lens profiles owned by the given Ethereum Address. Each request is + * Resolves Lens profiles owned by the given Ethereum Address. Each request is * limited by 10. To get other pages you must to pass the pageCursor parameter. - * + * * Lens profiles can be resolved on the Polygon Mainnet (matic) or Mumbai Testnet * (maticmum). Visit https://docs.lens.xyz/docs/api-links for more information. - * + * * @param address - Ethereum User address. - * @param pageCursor - Page cursor used to paginate the request. Default to - * first page. Visit https://docs.lens.xyz/docs/get-profiles#api-details for more + * @param pageCursor - Page cursor used to paginate the request. Default to + * first page. Visit https://docs.lens.xyz/docs/get-profiles#api-details for more * information. * @returns Object containing Lens profiles items and pagination info. */ @@ -141,9 +141,8 @@ export const ssxResolveLens = async ( /* Ethereum User Address. */ address: string, /* Page cursor used to paginate the request. Default to first page. */ - pageCursor: string = "{}" + pageCursor = '{}' ): Promise<SSXLensProfilesResponse | string> => { - if (!address) { throw new Error('Missing address.'); } @@ -157,18 +156,20 @@ export const ssxResolveLens = async ( let lens: { data: { profiles: SSXLensProfilesResponse } }; try { - lens = (await axios({ - url: apiURL, - method: 'post', - data: { - operationName: 'Profiles', - query: getProfilesQuery, - variables: { - addresses: [address], - cursor: pageCursor - } - }, - })).data; + lens = ( + await axios({ + url: apiURL, + method: 'post', + data: { + operationName: 'Profiles', + query: getProfilesQuery, + variables: { + addresses: [address], + cursor: pageCursor, + }, + }, + }) + ).data; } catch (err) { throw new Error(err?.response?.data?.errors ?? err); } diff --git a/packages/ssx-core/tests/utils.test.ts b/packages/ssx-core/tests/utils.test.ts index 2f73f163..9f4a9576 100644 --- a/packages/ssx-core/tests/utils.test.ts +++ b/packages/ssx-core/tests/utils.test.ts @@ -176,7 +176,9 @@ test('Should resolve Lens profile on Mainnet with a message advertising about th await expect( ssxResolveLens(provider, '0x96F7fB7ed32640d9D3a982f67CD6c09fc53EBEF1') - ).resolves.toEqual(`Can't resolve Lens to 0x96F7fB7ed32640d9D3a982f67CD6c09fc53EBEF1 on network 'homestead'. Use 'matic' (Polygon) or 'maticmum' (Mumbai) instead.`); + ).resolves.toEqual( + `Can't resolve Lens to 0x96F7fB7ed32640d9D3a982f67CD6c09fc53EBEF1 on network 'homestead'. Use 'matic' (Polygon) or 'maticmum' (Mumbai) instead.` + ); }, 30000); test('Should resolve Lens profile on Polygon Mainnet successfully', async () => { @@ -192,7 +194,7 @@ test('Should resolve Lens profile on Polygon Mainnet successfully', async () => pageInfo: expect.objectContaining({ prev: '{"offset":0}', next: '{"offset":1}', - }) + }), }) ); }, 30000); @@ -210,7 +212,7 @@ test('Should resolve Lens profile on Mumbai Testnet successfully', async () => { pageInfo: expect.objectContaining({ prev: '{"offset":0}', next: null, - }) + }), }) ); }, 30000); diff --git a/packages/ssx-react/src/index.ts b/packages/ssx-react/src/index.ts index d605c790..29724020 100644 --- a/packages/ssx-react/src/index.ts +++ b/packages/ssx-react/src/index.ts @@ -1,7 +1,7 @@ -export { - useSSX, - SSXProvider, - SSXContextInterface, - SSXProviderProps, - SSXWeb3Provider +export { + useSSX, + SSXProvider, + SSXContextInterface, + SSXProviderProps, + SSXWeb3Provider, } from './ssx.js'; diff --git a/packages/ssx-react/src/ssx.tsx b/packages/ssx-react/src/ssx.tsx index c035b91c..7408ae32 100644 --- a/packages/ssx-react/src/ssx.tsx +++ b/packages/ssx-react/src/ssx.tsx @@ -83,20 +83,20 @@ export const SSXProvider = ({ return ssx; } - const updateStateOnChangeProvider = async (ssx) => { - if(watchProvider) { + const updateStateOnChangeProvider = async ssx => { + if (watchProvider) { const newSSX = await watchProvider(provider, ssx); - if(newSSX) { + if (newSSX) { setSSX(newSSX); } } - } + }; useEffect(() => { - if(provider && !ssx) { + if (provider && !ssx) { initializeSSX(); } - if(ssx && provider) { + if (ssx && provider) { ssx.provider = provider.provider; } updateStateOnChangeProvider(ssx); diff --git a/packages/ssx-sdk/src/modules/Storage/KeplerStorage.ts b/packages/ssx-sdk/src/modules/Storage/KeplerStorage.ts index 0afed4a2..ac1b58e1 100644 --- a/packages/ssx-sdk/src/modules/Storage/KeplerStorage.ts +++ b/packages/ssx-sdk/src/modules/Storage/KeplerStorage.ts @@ -1,14 +1,13 @@ -import { initialized, kepler } from "@spruceid/ssx-sdk-wasm"; -import { - ConfigOverrides, - SSXClientSession, -} from "@spruceid/ssx-core/client"; +import { initialized, kepler, ssxSession } from '@spruceid/ssx-sdk-wasm'; +import { ConfigOverrides, SSXClientSession } from '@spruceid/ssx-core/client'; +import { generateNonce } from 'siwe'; import { OrbitConnection, activateSession, hostOrbit, Response, -} from "./kepler"; + Session, +} from './kepler'; import { IStorage, IKepler, @@ -16,16 +15,36 @@ import { IStoragePutOptions, IStorageGetOptions, IStorageDeleteOptions, -} from "./interfaces"; +} from './interfaces'; import { IUserAuthorization, UserAuthorizationConnected, SiweMessage, -} from "../../"; +} from '../../'; + +export type DelegateParams = { + /** The target file or folder you are sharing */ + target: string; + /** The DID of the key you are delegating to. */ + delegateDID: string; + /** The actions you are authorizing the delegate to do. */ + actions: string[]; + /** The statement in the SIWE message */ + statement?: string; +}; + +export type DelegateResponse = { + /** The contents of the SIWE message */ + siwe: string; + /** The signature of the SIWE message */ + signature: string; + /** The version of the delegation issued */ + version: number; +}; export class KeplerStorage implements IStorage, IKepler { - public namespace = "kepler"; - private prefix: string; + public namespace = 'kepler'; + public prefix: string; private hosts: string[]; private autoCreateNewOrbit: boolean; private userAuth: IUserAuthorization; @@ -36,13 +55,16 @@ export class KeplerStorage implements IStorage, IKepler { /** The connection to the orbit. */ private _orbit?: OrbitConnection; + /** Session Manager. Holds session keys and session objects */ + private sessionManager?: any; + /** The domain to display in the SIWE message. */ domain?: string; constructor(config: any, userAuth: IUserAuthorization) { this.userAuth = userAuth; - this.hosts = [...(config?.hosts || []), "https://kepler.spruceid.xyz"]; - this.prefix = config?.prefix || ""; + this.hosts = [...(config?.hosts || []), 'https://kepler.spruceid.xyz']; + this.prefix = config?.prefix || ''; this.autoCreateNewOrbit = config?.autoCreateNewOrbit === undefined ? true @@ -54,6 +76,7 @@ export class KeplerStorage implements IStorage, IKepler { ): Promise<ConfigOverrides> { await initialized; this.keplerModule = await kepler; + this.sessionManager = new (await ssxSession).SSXSessionManager(); (global as any).keplerModule = this.keplerModule; const address = await ssx.provider.getSigner().getAddress(); @@ -67,24 +90,24 @@ export class KeplerStorage implements IStorage, IKepler { public async targetedActions(): Promise<{ [target: string]: string[] }> { const actions = {}; - actions[`${this.orbitId}/capabilities/`] = ["read"]; + actions[`${this.orbitId}/capabilities/`] = ['read']; actions[`${this.orbitId}/kv/${this.prefix}`] = [ - "put", - "get", - "list", - "del", - "metadata", + 'put', + 'get', + 'list', + 'del', + 'metadata', ]; return actions; } public async generateKeplerSession( ssxSession: SSXClientSession - ): Promise<SSXClientSession> { + ): Promise<Session> { return await Promise.resolve({ jwk: JSON.parse(ssxSession.sessionKey), orbitId: this.orbitId, - service: "kv", + service: 'kv', siwe: ssxSession.siwe, signature: ssxSession.signature, verificationMethod: new SiweMessage(ssxSession.siwe).uri, @@ -95,9 +118,7 @@ export class KeplerStorage implements IStorage, IKepler { .then(JSON.parse); } - public async afterSignIn( - ssxSession: SSXClientSession - ): Promise<void> { + public async afterSignIn(ssxSession: SSXClientSession): Promise<void> { const keplerHost = this.hosts[0]; const session = await this.generateKeplerSession(ssxSession); @@ -124,7 +145,7 @@ export class KeplerStorage implements IStorage, IKepler { get orbit(): OrbitConnection { if (!this._orbit) { - throw new Error("KeplerStorage is not connected"); + throw new Error('KeplerStorage is not connected'); } return this._orbit; } @@ -152,16 +173,20 @@ export class KeplerStorage implements IStorage, IKepler { public async list( options: IStorageListOptions = { - prefix: this.prefix, removePrefix: false, } ): Promise<Response> { - const { prefix, path, request, removePrefix } = options; + const { + prefix = this.prefix, + path, + request, + removePrefix = false, + } = options; const p = path ? `${prefix}/${path}` : `${prefix}/`; const response = await this.orbit.list(prefix, request); // remove prefix from keys return removePrefix - ? { ...response, data: response.data.map((key) => key.slice(p.length)) } + ? { ...response, data: response.data.map(key => key.slice(p.length)) } : response; } @@ -176,7 +201,7 @@ export class KeplerStorage implements IStorage, IKepler { } public async deleteAll(prefix?: string): Promise<Response[]> { - if (!!prefix) { + if (prefix) { return this.orbit.deleteAll(`${this.prefix}/${prefix}`); } else { return this.orbit.deleteAll(this.prefix); @@ -195,7 +220,7 @@ export class KeplerStorage implements IStorage, IKepler { const session = await this.generateKeplerSession(ssxSession); const keplerHost = this.hosts[0]; - await activateSession(session, keplerHost).then((authn) => { + await activateSession(session, keplerHost).then(authn => { this._orbit = new OrbitConnection(keplerHost, authn); }); return true; @@ -205,9 +230,7 @@ export class KeplerStorage implements IStorage, IKepler { } } - public async hostOrbit( - ssxSession?: SSXClientSession - ): Promise<void> { + public async hostOrbit(ssxSession?: SSXClientSession): Promise<void> { const keplerHost = this.hosts[0]; const { status: hostStatus, statusText } = await hostOrbit( this.userAuth.getSigner(), @@ -222,30 +245,131 @@ export class KeplerStorage implements IStorage, IKepler { await this.activateSession(ssxSession, () => { throw new Error( - "Session not found. You must be signed in to host an orbit" + 'Session not found. You must be signed in to host an orbit' ); }); } - public async generateSharingLink(key: string, params?: any): Promise<string> { - // generate key // done - // delegate to key - // bundle key and delegation - // generate sharing link - return ""; - } + public async delegate({ + target, + delegateDID, + actions, + statement, + }: DelegateParams): Promise<DelegateResponse> { + // add actions to session builder + this.sessionManager.resetBuilder(); + this.sessionManager.addTargetedActions(this.namespace, target, actions); + + // create siwe message + const address = + this.userAuth?.address() || + (await this.userAuth.getSigner().getAddress()); + const chainId: number = + this.userAuth?.chainId() || + (await this.userAuth.getSigner().getChainId()); + const siweConfig = { + statement, + address, + walletAddress: address, + chainId, + domain: globalThis.location.hostname, + issuedAt: new Date().toISOString(), + nonce: generateNonce(), + }; + + // build and sign message + const siwe = await this.sessionManager.build(siweConfig, null, delegateDID); + const signature = await this.userAuth.signMessage(siwe); - public async retrieveSharingLink(link: string): Promise<Response> { - // read key and delegation bundle - // retrieve data with key return { - ok: true, - status: 200, - statusText: "ok", - headers: new Headers(), - data: {}, + siwe, + signature, + version: 1, }; } + + public async generateSharingLink( + path: string, + params?: any + ): Promise<string> { + // generate key + const allKeys = await this.sessionManager.listSessionKeys(); + const keyId = await this.sessionManager.createSessionKey( + `sharekey-${allKeys.length}` + ); + const sessionKey = this.sessionManager.jwk(keyId); + const delegateDID = await this.sessionManager.getDID(keyId); + + // get file target + permissions + const target = `${this.orbitId}/kv/${path}`; + const actions = ['get', 'metadata']; + + // delegate permission to target + const { siwe, signature } = await this.delegate({ + target, + delegateDID, + actions, + statement: 'I am giving permission to read this data.', + }); + + // create ssx + kepler session + const sessionData: SSXClientSession = { + address: this.userAuth.address(), + walletAddress: this.userAuth.address(), + chainId: this.userAuth.chainId(), + sessionKey, + siwe, + signature, + }; + + const session = await this.generateKeplerSession(sessionData); + /* activate session */ + // is this required? only for revocation? @chunningham + const keplerHost = this.hosts[0]; + await activateSession(session, keplerHost).catch(({ status, msg }) => { + if (status !== 404) { + throw new Error( + `Failed to submit session key delegation to Kepler: ${msg}` + ); + } + }); + /* end activate session */ + + // store session with key + // bundle delegation and encode + const shareData = { + path, + keplerHost: this.hosts[0], + session, + }; + + const shareJSON = JSON.stringify(shareData); + const shareBase64 = btoa(shareJSON); + return shareBase64; + } + + public async retrieveSharingLink(encodedShare: string): Promise<Response> { + (global as any).keplerModule = await kepler; + + // read key and delegation bundle + const shareJSON = atob(encodedShare); + const { path, keplerHost, session } = JSON.parse(shareJSON); + + // activate session and retrieve data + try { + const authn = await activateSession(session, keplerHost); + const orbit = new OrbitConnection(keplerHost, authn); + const response = await orbit.get(path); + return response; + } catch (error) { + const { status, msg } = error; + if (status !== 404) { + throw new Error( + `Failed to submit session key delegation to Kepler: ${msg}` + ); + } + } + } } export default KeplerStorage; diff --git a/packages/ssx-sdk/src/modules/Storage/interfaces.ts b/packages/ssx-sdk/src/modules/Storage/interfaces.ts index f2b02173..98619cc8 100644 --- a/packages/ssx-sdk/src/modules/Storage/interfaces.ts +++ b/packages/ssx-sdk/src/modules/Storage/interfaces.ts @@ -1,38 +1,38 @@ import { SSXClientSession, SSXExtension } from '@spruceid/ssx-core/client'; -import type { Request, Response } from './kepler'; +import type { Request, Response, Session } from './kepler'; -/** +/** * @interface IStorageBaseOptions * @property prefix - Optional string identifying the folder in the storage. * @property request - Optional request object to use for the operation. */ export interface IStorageBaseOptions { - prefix?: string, - request?: Request + prefix?: string; + request?: Request; } -/** +/** * @interface IStorageGetOptions * @property prefix - Optional string identifying the folder in the storage. * @property request - Optional request object to use for the operation. */ -export interface IStorageGetOptions extends IStorageBaseOptions { } +export interface IStorageGetOptions extends IStorageBaseOptions {} -/** +/** * @interface IStoragePutOptions * @property prefix - Optional string identifying the folder in the storage. * @property request - Optional request object to use for the operation. */ -export interface IStoragePutOptions extends IStorageBaseOptions { } +export interface IStoragePutOptions extends IStorageBaseOptions {} -/** +/** * @interface IStorageDeleteOptions * @property prefix - Optional string identifying the folder in the storage. * @property request - Optional request object to use for the operation. */ -export interface IStorageDeleteOptions extends IStorageBaseOptions { } +export interface IStorageDeleteOptions extends IStorageBaseOptions {} -/** +/** * @interface IStorageListOptions * @property prefix - Optional string identifying the folder in the storage. * @property path - Optional string identifying the path to be combined with the prefix in the storage. @@ -40,8 +40,8 @@ export interface IStorageDeleteOptions extends IStorageBaseOptions { } * @property request - Optional request object to use for the operation. */ export interface IStorageListOptions extends IStorageBaseOptions { - path?: string, - removePrefix?: boolean + path?: string; + removePrefix?: boolean; } /** @@ -54,10 +54,7 @@ interface IStorage extends SSXExtension { * @param options - IStorageGetOptions object. * @returns A Promise that resolves to the value associated with the given key or undefined if the key does not exist. */ - get( - key: string, - options?: IStorageGetOptions - ): Promise<Response>; + get(key: string, options?: IStorageGetOptions): Promise<Response>; /** * Stores a value with the specified key. @@ -66,11 +63,7 @@ interface IStorage extends SSXExtension { * @param options - IStoragePutOptions object. * @returns A Promise that resolves when the operation is complete. */ - put( - key: string, - value: any, - options?: IStoragePutOptions - ): Promise<Response>; + put(key: string, value: any, options?: IStoragePutOptions): Promise<Response>; /** * Lists all keys currently stored in the storage. @@ -91,10 +84,7 @@ interface IStorage extends SSXExtension { * @param options - IStorageDeleteOptions object. * @returns A Promise that resolves when the operation is complete. */ - delete( - key: string, - options?: IStorageListOptions - ): Promise<Response>; + delete(key: string, options?: IStorageListOptions): Promise<Response>; /** * Deletes all stored key-value pairs in the storage. @@ -113,9 +103,7 @@ export interface IKepler extends IStorage { ): Promise<boolean>; generateSharingLink(key: string, params?: any): Promise<string>; retrieveSharingLink(link: string): Promise<Response>; - generateKeplerSession( - ssxSession: SSXClientSession - ): Promise<SSXClientSession>; + generateKeplerSession(ssxSession: SSXClientSession): Promise<Session>; } /** @@ -143,8 +131,4 @@ interface IKeplerStorageConfig extends IStorageConfig { autoCreateNewOrbit?: boolean; } -export { - IStorage, - IStorageConfig, - IKeplerStorageConfig, -}; +export { IStorage, IStorageConfig, IKeplerStorageConfig }; diff --git a/packages/ssx-sdk/src/modules/Storage/kepler/authenticator.ts b/packages/ssx-sdk/src/modules/Storage/kepler/authenticator.ts index ac08d2d9..1128989a 100644 --- a/packages/ssx-sdk/src/modules/Storage/kepler/authenticator.ts +++ b/packages/ssx-sdk/src/modules/Storage/kepler/authenticator.ts @@ -5,9 +5,7 @@ import { prepareSession, } from './module'; import { WalletProvider } from './walletProvider'; - -type SessionConfig = any; -type Session = any; +import { SessionConfig, Session } from './types'; export async function startSession( wallet: WalletProvider, diff --git a/packages/ssx-sdk/src/modules/Storage/kepler/index.ts b/packages/ssx-sdk/src/modules/Storage/kepler/index.ts index 7dbdd8d4..2fb19ade 100644 --- a/packages/ssx-sdk/src/modules/Storage/kepler/index.ts +++ b/packages/ssx-sdk/src/modules/Storage/kepler/index.ts @@ -2,3 +2,4 @@ export { Kepler, KeplerOptions } from './kepler'; export { OrbitConnection, Request, Response, hostOrbit } from './orbit'; export { Bytes, WalletProvider } from './walletProvider'; export { activateSession } from './authenticator'; +export { SessionConfig, Session, HostConfig } from './types'; diff --git a/packages/ssx-sdk/src/modules/Storage/kepler/kepler.ts b/packages/ssx-sdk/src/modules/Storage/kepler/kepler.ts index f4f73298..9ba54059 100644 --- a/packages/ssx-sdk/src/modules/Storage/kepler/kepler.ts +++ b/packages/ssx-sdk/src/modules/Storage/kepler/kepler.ts @@ -1,9 +1,8 @@ -// import { SessionConfig } from "."; import { startSession, activateSession } from './authenticator'; import { hostOrbit, OrbitConnection } from './orbit'; import { WalletProvider } from './walletProvider'; +import { SessionConfig } from './types'; -type SessionConfig = any; /** Configuration for [[Kepler]]. */ export type KeplerOptions = { /** The Kepler hosts that you wish to connect to. diff --git a/packages/ssx-sdk/src/modules/Storage/kepler/orbit.ts b/packages/ssx-sdk/src/modules/Storage/kepler/orbit.ts index 66161215..d565bcd5 100644 --- a/packages/ssx-sdk/src/modules/Storage/kepler/orbit.ts +++ b/packages/ssx-sdk/src/modules/Storage/kepler/orbit.ts @@ -3,9 +3,7 @@ import { KV } from './kv'; import { generateHostSIWEMessage, siweToDelegationHeaders } from './module'; import { WalletProvider } from './walletProvider'; import { Capabilities, CapSummary } from './capabilities'; - -// @TODO: define HostConfig type -type HostConfig = any; +import { HostConfig } from './types'; /** * a connection to an orbit in a Kepler instance. @@ -280,9 +278,9 @@ export const hostOrbit = async ( const address = await wallet.getAddress(); const chainId = await wallet.getChainId(); const issuedAt = new Date(Date.now()).toISOString(); - const peerId = await fetch(keplerUrl + `/peer/generate/${encodeURIComponent(orbitId)}`).then( - (res: FetchResponse) => res.text() - ); + const peerId = await fetch( + keplerUrl + `/peer/generate/${encodeURIComponent(orbitId)}` + ).then((res: FetchResponse) => res.text()); const config: HostConfig = { address, chainId, diff --git a/packages/ssx-sdk/src/modules/Storage/kepler/types.ts b/packages/ssx-sdk/src/modules/Storage/kepler/types.ts new file mode 100644 index 00000000..94d8f625 --- /dev/null +++ b/packages/ssx-sdk/src/modules/Storage/kepler/types.ts @@ -0,0 +1,59 @@ +/** + * Configuration object for starting a Kepler session. + */ +export type SessionConfig = { + /** Actions that the session key will be permitted to perform, organized by service and path */ + actions: { [service: string]: { [key: string]: string[] } }; + /** Ethereum address. */ + address: string; + /** Chain ID. */ + chainId: number; + /** Domain of the webpage. */ + domain: string; + /** Current time for SIWE message. */ + issuedAt: string; + /** The orbit that is the target resource of the delegation. */ + orbitId: string; + /** The earliest time that the session will be valid from. */ + notBefore?: string; + /** The latest time that the session will be valid until. */ + expirationTime: string; + /** Optional parent delegations to inherit and attenuate */ + parents?: string[]; + /** Optional jwk to delegate to */ + jwk?: object; +}; + +/** + * A Kepler session. + */ +export type Session = { + /** The delegation from the user to the session key. */ + delegationHeader: { Authorization: string }; + /** The delegation reference from the user to the session key. */ + delegationCid: string; + /** The session key. */ + jwk: object; + /** The orbit that the session key is permitted to perform actions against. */ + orbitId: string; + /** The verification method of the session key. */ + verificationMethod: string; +}; + +/** + * Configuration object for generating a Orbit Host Delegation SIWE message. + */ +export type HostConfig = { + /** Ethereum address. */ + address: string; + /** Chain ID. */ + chainId: number; + /** Domain of the webpage. */ + domain: string; + /** Current time for SIWE message. */ + issuedAt: string; + /** The orbit that is the target resource of the delegation. */ + orbitId: string; + /** The peer that is the target/invoker in the delegation. */ + peerId: string; +}; diff --git a/packages/ssx-sdk/src/modules/UserAuthorization.ts b/packages/ssx-sdk/src/modules/UserAuthorization.ts index 3f44139b..ace7f82c 100644 --- a/packages/ssx-sdk/src/modules/UserAuthorization.ts +++ b/packages/ssx-sdk/src/modules/UserAuthorization.ts @@ -1,11 +1,8 @@ -import { providers, Signer } from "ethers"; -import { - initialized, - ssxSession, -} from "@spruceid/ssx-sdk-wasm"; -import merge from "lodash.merge"; -import axios, { AxiosInstance } from "axios"; -import { generateNonce } from "siwe"; +import { providers, Signer } from 'ethers'; +import { initialized, ssxSession } from '@spruceid/ssx-sdk-wasm'; +import merge from 'lodash.merge'; +import axios, { AxiosInstance } from 'axios'; +import { generateNonce } from 'siwe'; import { SSXEnsData, ssxResolveEns, @@ -13,14 +10,14 @@ import { SSXLensProfilesResponse, SSXEnsResolveOptions, isSSXRouteConfig, -} from "@spruceid/ssx-core"; +} from '@spruceid/ssx-core'; import { SSXClientSession, SSXClientConfig, ISSXConnected, SSXExtension, GnosisDelegation, -} from "@spruceid/ssx-core/client"; +} from '@spruceid/ssx-core/client'; /** UserAuthorization Module * @@ -116,7 +113,9 @@ class UserAuthorizationInit { // eslint-disable-next-line no-underscore-dangle if (!this.config.providers.web3.driver?._isProvider) { try { - provider = new providers.Web3Provider(this.config.providers.web3.driver); + provider = new providers.Web3Provider( + this.config.providers.web3.driver + ); } catch (err) { // Provider creation error console.error(err); @@ -127,12 +126,12 @@ class UserAuthorizationInit { } if ( - !this.config.providers.web3?.driver?.bridge?.includes("walletconnect") + !this.config.providers.web3?.driver?.bridge?.includes('walletconnect') ) { const connectedAccounts = await provider.listAccounts(); if (connectedAccounts.length === 0) { try { - await provider.send("wallet_requestPermissions", [ + await provider.send('wallet_requestPermissions', [ { eth_accounts: {} }, ]); } catch (err) { @@ -173,7 +172,7 @@ class UserAuthorizationConnected implements ISSXConnected { /** Verifies if extension is enabled. */ public isExtensionEnabled = (namespace: string) => - this.extensions.filter((e) => e.namespace === namespace).length === 1; + this.extensions.filter(e => e.namespace === namespace).length === 1; /** Axios instance. */ public api?: AxiosInstance; @@ -251,11 +250,8 @@ class UserAuthorizationConnected implements ISSXConnected { * @param params - Request params. * @returns Promise with nonce. */ - public async ssxServerNonce( - params: Record<string, any> - ): Promise<string> { - const route = - this.config.providers?.server?.routes?.nonce ?? "/ssx-nonce"; + public async ssxServerNonce(params: Record<string, any>): Promise<string> { + const route = this.config.providers?.server?.routes?.nonce ?? '/ssx-nonce'; const requestConfig = isSSXRouteConfig(route) ? { customAPIOperation: undefined, @@ -277,8 +273,8 @@ class UserAuthorizationConnected implements ISSXConnected { try { nonce = ( await this.api.request({ - method: "get", - url: "/ssx-nonce", + method: 'get', + url: '/ssx-nonce', ...requestConfig, params, }) @@ -288,7 +284,7 @@ class UserAuthorizationConnected implements ISSXConnected { throw error; } if (!nonce) { - throw new Error("Unable to retrieve nonce from server."); + throw new Error('Unable to retrieve nonce from server.'); } return nonce; } @@ -299,11 +295,8 @@ class UserAuthorizationConnected implements ISSXConnected { * @param session - SSXClientSession object. * @returns Promise with server session data. */ - public async ssxServerLogin( - session: SSXClientSession - ): Promise<any> { - const route = - this.config.providers?.server?.routes?.login ?? "/ssx-login"; + public async ssxServerLogin(session: SSXClientSession): Promise<any> { + const route = this.config.providers?.server?.routes?.login ?? '/ssx-login'; const requestConfig = isSSXRouteConfig(route) ? { customAPIOperation: undefined, @@ -322,13 +315,13 @@ class UserAuthorizationConnected implements ISSXConnected { if (this.api) { let resolveEns: boolean | SSXEnsResolveOptions = false; if ( - typeof this.config.resolveEns === "object" && + typeof this.config.resolveEns === 'object' && this.config.resolveEns.resolveOnServer ) { resolveEns = this.config.resolveEns.resolve; } - const resolveLens: boolean = this.config.resolveLens === "onServer"; + const resolveLens: boolean = this.config.resolveLens === 'onServer'; try { const data = { @@ -337,19 +330,19 @@ class UserAuthorizationConnected implements ISSXConnected { address: session.address, walletAddress: session.walletAddress, chainId: session.chainId, - daoLogin: this.isExtensionEnabled("delegationRegistry"), + daoLogin: this.isExtensionEnabled('delegationRegistry'), resolveEns, resolveLens, }; // @TODO(w4ll3): figure out how to send a custom sessionKey return this.api .request({ - method: "post", - url: "/ssx-login", + method: 'post', + url: '/ssx-login', ...requestConfig, data, }) - .then((response) => response.data); + .then(response => response.data); } catch (error) { console.error(error); throw error; @@ -368,7 +361,7 @@ class UserAuthorizationConnected implements ISSXConnected { await this.afterConnectHooksPromise; const sessionKey = this.builder.jwk(); if (sessionKey === undefined) { - return Promise.reject(new Error("unable to retrieve session key")); + return Promise.reject(new Error('unable to retrieve session key')); } const signer = await this.provider.getSigner(); const walletAddress = await signer.getAddress(); @@ -416,7 +409,7 @@ class UserAuthorizationConnected implements ISSXConnected { async signOut(session: SSXClientSession): Promise<void> { // get request configuration const route = - this.config.providers?.server?.routes?.logout ?? "/ssx-logout"; + this.config.providers?.server?.routes?.logout ?? '/ssx-logout'; const requestConfig = isSSXRouteConfig(route) ? { customAPIOperation: undefined, @@ -438,8 +431,8 @@ class UserAuthorizationConnected implements ISSXConnected { const data = { ...session }; await this.api.request({ - method: "post", - url: "/ssx-logout", + method: 'post', + url: '/ssx-logout', ...requestConfig, data, }); @@ -474,9 +467,7 @@ class UserAuthorization implements IUserAuthorization { /** The SSXClientConfig object. */ private config: SSXClientConfig; - constructor( - private _config: SSXClientConfig = SSX_DEFAULT_CONFIG - ) { + constructor(private _config: SSXClientConfig = SSX_DEFAULT_CONFIG) { this.config = _config; this.init = new UserAuthorizationInit({ ...this.config, @@ -574,11 +565,7 @@ class UserAuthorization implements IUserAuthorization { avatar: true, } ): Promise<SSXEnsData> { - return ssxResolveEns( - this.connection.provider, - address, - resolveEnsOpts - ); + return ssxResolveEns(this.connection.provider, address, resolveEnsOpts); } /** @@ -598,7 +585,7 @@ class UserAuthorization implements IUserAuthorization { /* Ethereum User Address. */ address: string, /* Page cursor used to paginate the request. Default to first page. */ - pageCursor = "{}" + pageCursor = '{}' ): Promise<string | SSXLensProfilesResponse> { return ssxResolveLens(this.connection.provider, address, pageCursor); } diff --git a/packages/ssx-sdk/src/ssx.ts b/packages/ssx-sdk/src/ssx.ts index 2d875cbf..7721bdcc 100644 --- a/packages/ssx-sdk/src/ssx.ts +++ b/packages/ssx-sdk/src/ssx.ts @@ -3,18 +3,18 @@ import { SSXEnsData, SSXEnsResolveOptions, SSXLensProfilesResponse, -} from "@spruceid/ssx-core"; +} from '@spruceid/ssx-core'; import { IUserAuthorization, KeplerStorage, UserAuthorization, -} from "./modules"; +} from './modules'; import { SSXClientConfig, SSXClientSession, SSXExtension, -} from "@spruceid/ssx-core/client"; -import type { providers, Signer } from "ethers"; +} from '@spruceid/ssx-core/client'; +import type { providers, Signer } from 'ethers'; declare global { interface Window { @@ -79,14 +79,14 @@ export class SSX { config?.modules?.storage === undefined ? false : config.modules.storage; if (storageConfig !== false) { - if (typeof storageConfig === "object") { + if (typeof storageConfig === 'object') { // Initialize storage with the provided config this.storage = new KeplerStorage(storageConfig, this.userAuthorization); } else { // storage == true or undefined // Initialize storage with default config when no other condition is met this.storage = new KeplerStorage( - { prefix: "ssx" }, + { prefix: 'ssx' }, this.userAuthorization ); } @@ -150,13 +150,13 @@ export class SSX { /* Ethereum User Address. */ address: string, /* Page cursor used to paginate the request. Default to first page. */ - pageCursor = "{}" + pageCursor = '{}' ): Promise<string | SSXLensProfilesResponse> { return this.userAuthorization.resolveLens(address, pageCursor); } /** - * Gets the session representation (once signed in). + * Gets the session representation (once signed in). * @returns Address. */ public session: () => SSXClientSession | undefined = () => diff --git a/packages/ssx-sdk/webpack.config.js b/packages/ssx-sdk/webpack.config.js index cd6d6ea0..1d8fe8fa 100644 --- a/packages/ssx-sdk/webpack.config.js +++ b/packages/ssx-sdk/webpack.config.js @@ -35,7 +35,7 @@ module.exports = { library: '@spruceid/ssx', libraryTarget: 'umd', umdNamedDefine: true, - globalObject: 'this' + globalObject: 'this', }, plugins: [ new webpack.ProvidePlugin({ diff --git a/packages/ssx-server/src/middlewares/express/endpoints.ts b/packages/ssx-server/src/middlewares/express/endpoints.ts index 6ed9c6ea..35d71d77 100644 --- a/packages/ssx-server/src/middlewares/express/endpoints.ts +++ b/packages/ssx-server/src/middlewares/express/endpoints.ts @@ -1,13 +1,13 @@ import express from 'express'; import { Request, Response } from 'express'; -import { SSXServerRoutes, isSSXServerMiddlewareConfig } from '@spruceid/ssx-core'; +import { + SSXServerRoutes, + isSSXServerMiddlewareConfig, +} from '@spruceid/ssx-core'; import { SSXServerBaseClass } from '@spruceid/ssx-core/server'; import { getRoutePath } from '../utils'; -const ssxEndpoints = ( - ssx: SSXServerBaseClass, - routes?: SSXServerRoutes -) => { +const ssxEndpoints = (ssx: SSXServerBaseClass, routes?: SSXServerRoutes) => { const router = express.Router(); /** @@ -25,9 +25,11 @@ const ssxEndpoints = ( req.session.siwe = undefined; req.session.nonce = ssx.generateNonce(); req.session.save(() => res.status(200).send(req.session.nonce)); - isSSXServerMiddlewareConfig(routes?.nonce) ? routes?.nonce?.callback(req) : null; + isSSXServerMiddlewareConfig(routes?.nonce) + ? routes?.nonce?.callback(req) + : null; return; - } + }, ); /** @@ -69,7 +71,7 @@ const ssxEndpoints = ( req.body.daoLogin, req.body.resolveEns, req.session.nonce, - req.body.resolveLens + req.body.resolveLens, ); } catch (error) { return res.status(500).json({ message: error.message }); @@ -91,9 +93,11 @@ const ssxEndpoints = ( req.session.ens = session.ens; req.session.lens = session.lens; req.session.save(() => res.status(200).json({ ...req.session })); - isSSXServerMiddlewareConfig(routes?.login) ? routes?.login?.callback(req) : null; + isSSXServerMiddlewareConfig(routes?.login) + ? routes?.login?.callback(req) + : null; return; - } + }, ); /** @@ -117,9 +121,11 @@ const ssxEndpoints = ( res.status(500).json({ message: error.message }); } res.status(204).send(); - isSSXServerMiddlewareConfig(routes?.logout) ? routes?.logout?.callback(req) : null; + isSSXServerMiddlewareConfig(routes?.logout) + ? routes?.logout?.callback(req) + : null; return; - } + }, ); return router; }; diff --git a/packages/ssx-server/src/middlewares/express/index.ts b/packages/ssx-server/src/middlewares/express/index.ts index 393fbf32..ddfd5932 100644 --- a/packages/ssx-server/src/middlewares/express/index.ts +++ b/packages/ssx-server/src/middlewares/express/index.ts @@ -4,7 +4,6 @@ import { ssxMiddleware, SSXAuthenticated } from './middleware'; import { SSXServerRoutes } from '@spruceid/ssx-core'; import { SSXServerBaseClass } from '@spruceid/ssx-core/server'; - /** * This middleware function has two key functions: * 1. It provides 3 endpoints for the client to hit: /ssx-nonce, /ssx-login, and /ssx-logout. These endpoints are used to authenticate the SIWE message and issue sessions. @@ -12,7 +11,10 @@ import { SSXServerBaseClass } from '@spruceid/ssx-core/server'; * * @param ssx - The SSX server instance. */ -const SSXExpressMiddleware = (ssx: SSXServerBaseClass, routes?: SSXServerRoutes) => { +const SSXExpressMiddleware = ( + ssx: SSXServerBaseClass, + routes?: SSXServerRoutes, +) => { return [ ssx.session, bodyParser.json(), diff --git a/packages/ssx-server/src/middlewares/express/middleware.ts b/packages/ssx-server/src/middlewares/express/middleware.ts index 39302cb3..3f6ff6c7 100644 --- a/packages/ssx-server/src/middlewares/express/middleware.ts +++ b/packages/ssx-server/src/middlewares/express/middleware.ts @@ -5,10 +5,7 @@ import { SiweMessage, SiweGnosisVerify, } from '@spruceid/ssx-core'; -import { - SSXLogFields, - SSXServerBaseClass, -} from '@spruceid/ssx-core/server'; +import { SSXLogFields, SSXServerBaseClass } from '@spruceid/ssx-core/server'; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -103,7 +100,6 @@ export const ssxMiddleware = (ssx: SSXServerBaseClass) => { verificationFallback: daoLogin ? SiweGnosisVerify : null, provider: ssx.provider, }, - ) .then((data) => ({ success: true, data })) .catch((error) => ({ success: false, error, data: null })); diff --git a/packages/ssx-server/src/middlewares/http/index.ts b/packages/ssx-server/src/middlewares/http/index.ts index 6301a7fb..02610acd 100644 --- a/packages/ssx-server/src/middlewares/http/index.ts +++ b/packages/ssx-server/src/middlewares/http/index.ts @@ -2,10 +2,10 @@ import { SiweMessage } from 'siwe'; import { Session, SessionData } from 'express-session'; import { IncomingMessage, ServerResponse } from 'http'; import { SSXRequestObject } from '../express/middleware'; -import { - isSSXServerMiddlewareConfig, +import { + isSSXServerMiddlewareConfig, SSXServerRoutes, - SiweGnosisVerify + SiweGnosisVerify, } from '@spruceid/ssx-core'; import { SSXServerBaseClass } from '@spruceid/ssx-core/server'; import { getRoutePath } from '../utils'; @@ -46,7 +46,10 @@ function getBody(req: IncomingMessage): Promise<any> { * @param ssx - The SSX server instance. * @returns requestListener: function (req: Request, res: Response) =\> (req: IncomingMessage, res: ServerResponse) */ -export const SSXHttpMiddleware = (ssx: SSXServerBaseClass, routes?: SSXServerRoutes) => { +export const SSXHttpMiddleware = ( + ssx: SSXServerBaseClass, + routes?: SSXServerRoutes, +) => { // eslint-disable-next-line @typescript-eslint/no-empty-function return (requestListener = (req, res) => {}) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -86,13 +89,15 @@ export const SSXHttpMiddleware = (ssx: SSXServerBaseClass, routes?: SSXServerRou req.session.destroy(() => {}); } } - const { pathname } = url.parse(req.url) + const { pathname } = url.parse(req.url); // ssx endpoints if (pathname === getRoutePath(routes?.nonce, '/ssx-nonce')) { req.session.nonce = ssx.generateNonce(); res.statusCode = 200; res.end(req.session.nonce); - isSSXServerMiddlewareConfig(routes?.nonce) ? routes?.nonce?.callback(req) : null; + isSSXServerMiddlewareConfig(routes?.nonce) + ? routes?.nonce?.callback(req) + : null; return; } else if (pathname === getRoutePath(routes?.login, '/ssx-login')) { // get body data @@ -145,7 +150,9 @@ export const SSXHttpMiddleware = (ssx: SSXServerBaseClass, routes?: SSXServerRou res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ ...req.session })); - isSSXServerMiddlewareConfig(routes?.login) ? routes?.login?.callback(req, body) : null; + isSSXServerMiddlewareConfig(routes?.login) + ? routes?.login?.callback(req, body) + : null; } else if (pathname === getRoutePath(routes?.logout, '/ssx-logout')) { req.session.destroy(null); req.session = null; @@ -153,7 +160,9 @@ export const SSXHttpMiddleware = (ssx: SSXServerBaseClass, routes?: SSXServerRou res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ success: true })); - isSSXServerMiddlewareConfig(routes?.logout) ? routes?.logout?.callback(req) : null; + isSSXServerMiddlewareConfig(routes?.logout) + ? routes?.logout?.callback(req) + : null; } // run user defined requestListener diff --git a/packages/ssx-server/src/middlewares/index.ts b/packages/ssx-server/src/middlewares/index.ts index fb8a0533..9d676d59 100644 --- a/packages/ssx-server/src/middlewares/index.ts +++ b/packages/ssx-server/src/middlewares/index.ts @@ -1,8 +1,3 @@ -export { - SSXExpressMiddleware, - SSXAuthenticated -} from './express'; +export { SSXExpressMiddleware, SSXAuthenticated } from './express'; -export { - SSXHttpMiddleware, -} from './http'; +export { SSXHttpMiddleware } from './http'; diff --git a/packages/ssx-server/src/middlewares/utils.ts b/packages/ssx-server/src/middlewares/utils.ts index 30bb4a5f..bce37006 100644 --- a/packages/ssx-server/src/middlewares/utils.ts +++ b/packages/ssx-server/src/middlewares/utils.ts @@ -1,4 +1,7 @@ -import { isSSXServerMiddlewareConfig, SSXServerRouteEndpointType } from "@spruceid/ssx-core"; +import { + isSSXServerMiddlewareConfig, + SSXServerRouteEndpointType, +} from '@spruceid/ssx-core'; /** * This receives a routeConfig param and returns the path string. @@ -6,7 +9,10 @@ import { isSSXServerMiddlewareConfig, SSXServerRouteEndpointType } from "@spruce * @param defaultPath - Default path string * @returns a path string */ -export const getRoutePath = (routeConfig: SSXServerRouteEndpointType, defaultPath: string) => { +export const getRoutePath = ( + routeConfig: SSXServerRouteEndpointType, + defaultPath: string, +) => { if (isSSXServerMiddlewareConfig(routeConfig)) { return routeConfig.path; } else if (typeof routeConfig === 'string') { diff --git a/packages/ssx-server/src/server.ts b/packages/ssx-server/src/server.ts index 6217a466..d702cc74 100644 --- a/packages/ssx-server/src/server.ts +++ b/packages/ssx-server/src/server.ts @@ -234,7 +234,7 @@ export class SSXServer extends SSXServerBaseClass { /* Ethereum User Address. */ address: string, /* Page cursor used to paginate the request. Default to first page. */ - pageCursor: string = '{}', + pageCursor = '{}', ): Promise<string | SSXLensProfilesResponse> { return ssxResolveLens(this.provider, address, pageCursor); } diff --git a/scripts/toggle-packages.js b/scripts/toggle-packages.js index 1f7ae4d4..9172d858 100644 --- a/scripts/toggle-packages.js +++ b/scripts/toggle-packages.js @@ -13,7 +13,7 @@ const writePackages = async packages => { } }; -const getAllPackageJSON = async (includeExamples=false) => { +const getAllPackageJSON = async (includeExamples = false) => { // read package.json const packageList = []; const packages = {}; diff --git a/tests/e2e/specs/user-stories.spec.js b/tests/e2e/specs/user-stories.spec.js index e0e6c5f0..dc67d1a3 100644 --- a/tests/e2e/specs/user-stories.spec.js +++ b/tests/e2e/specs/user-stories.spec.js @@ -8,13 +8,13 @@ describe('SSX', () => { // sign in cy.get('#signInButton').click(); cy.acceptMetamaskAccess({ - signInSignature: true + signInSignature: true, }).then(connected => { expect(connected).to.be.true; }); cy.get('#userAddress').should( 'have.text', - '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266' ); // sign out @@ -34,7 +34,7 @@ describe('SSX', () => { }); cy.get('#userAddress').should( 'have.text', - '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266' ); // sign out @@ -61,7 +61,7 @@ describe('SSX', () => { }); cy.get('#userAddress').should( 'have.text', - '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266' ); // sign out @@ -91,11 +91,8 @@ describe('SSX', () => { expect(confirmed).to.be.true; }); cy.get('#userAddress', { - defaultCommandTimeout: 60000 - }).should( - 'have.text', - '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', - ); + defaultCommandTimeout: 60000, + }).should('have.text', '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'); // sign out cy.get('#signOutButton').click(); @@ -124,7 +121,7 @@ describe('SSX', () => { }); cy.get('#userAddress').should( 'have.text', - '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266' ); // sign out @@ -152,7 +149,7 @@ describe('SSX', () => { }); cy.get('#userAddress').should( 'have.text', - '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266' ); // sign out @@ -165,7 +162,7 @@ describe('SSX', () => { cy.get('#resolveEns-Off').click(); cy.get('#selectPreferences').click(); }); - + it(`Story 7 - Users should be able to sign in with Ethereum using MetaMask with DAO Login, resolve ENS on server and SIWE Config.`, () => { // configure settings cy.get('#selectPreferences').click(); @@ -183,11 +180,8 @@ describe('SSX', () => { expect(confirmed).to.be.true; }); cy.get('#userAddress', { - timeout: 60000 - }).should( - 'have.text', - '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', - ); + timeout: 60000, + }).should('have.text', '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'); // sign out cy.get('#signOutButton').click(); @@ -202,6 +196,5 @@ describe('SSX', () => { cy.get('#siweConfig-Off').click(); cy.get('#selectPreferences').click(); }); - }); -}); \ No newline at end of file +}); diff --git a/tests/e2e/support.js b/tests/e2e/support.js index 8345c6e5..ff521fec 100644 --- a/tests/e2e/support.js +++ b/tests/e2e/support.js @@ -1 +1 @@ -import '@synthetixio/synpress/support/index'; \ No newline at end of file +import '@synthetixio/synpress/support/index'; diff --git a/yarn.lock b/yarn.lock index ed0e36e4..05f2a0c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3644,6 +3644,11 @@ resolved "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz#af85eb080f6934580e4d3b58046026b6c2b18717" integrity sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng== +"@remix-run/router@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.2.tgz#cba1cf0a04bc04cb66027c51fa600e9cbc388bc8" + integrity sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A== + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -17675,6 +17680,21 @@ react-refresh@^0.11.0: resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== +react-router-dom@^6.14.0: + version "6.14.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.2.tgz#88f520118b91aa60233bd08dbd3fdcaea3a68488" + integrity sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg== + dependencies: + "@remix-run/router" "1.7.2" + react-router "6.14.2" + +react-router@6.14.2: + version "6.14.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.2.tgz#1f60994d8c369de7b8ba7a78d8f7ec23df76b300" + integrity sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ== + dependencies: + "@remix-run/router" "1.7.2" + react-scripts@5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003"