Skip to content

Commit

Permalink
dapp caching (#1406)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Holtzman <[email protected]>
  • Loading branch information
floating and mholtzman authored Feb 11, 2023
1 parent ee67c36 commit d27140e
Show file tree
Hide file tree
Showing 16 changed files with 718 additions and 143 deletions.
3 changes: 3 additions & 0 deletions @types/frame/state.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,11 @@ interface Dapp {
ens: string
status?: string
config: Record<string, string>
content?: string // IPFS hash
manifest?: any
current?: any
openWhenReady: boolean
checkStatusRetryCount: number
}

type SignerType = 'ring' | 'seed' | 'trezor' | 'ledger' | 'lattice'
Expand Down
23 changes: 7 additions & 16 deletions app/dapp/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ class App extends React.Component {
return (
<div className='splash'>
<Native />
<div className='overlay' />
<div className='mainLeft'>
{/* <div className='overlay' /> */}
{/* <div className='mainLeft'>
<div
className='accountTile'
onClick={() => {
Expand All @@ -64,36 +64,27 @@ class App extends React.Component {
})}
</div>
</div>
</div> */}
</div>
</div>
</div> */}
<div className='main'>
<div className='mainTop' />
{currentDapp ? (
<>
<div
{/* <div
className='mainDappBackground'
style={{
background: currentDapp.colors ? currentDapp.colors.background : 'none'
}}
>
<div className='mainDappBackgroundTop' />
{!currentView.ready ? (
<div className='mainDappLoading'>
<div className='loader' style={loaderStyle} />
</div>
) : null}
</div>
</div> */}
</>
) : !currentView.ready ? (
sendDapp.status === 'failed' ? (
<div className='mainDappLoading'>
<div className='mainDappLoadingText'>{'Send dapp failed to load'}</div>
</div>
) : (
<div className='mainDappLoading'>
<div className='loader' />
</div>
)
) : null
) : null}
</div>
</div>
Expand Down
1 change: 0 additions & 1 deletion app/dapp/index.dev.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
<link rel="stylesheet" href="./index.styl" type="text/css" />
</head>
<body>
<div class="frameOverlay"></div>
<div id="dapp"></div>
<script type="module" src="./index.js" nonce="8c7d2664-ae99-42c2-ae12-3304e9168f71"></script>
</body>
Expand Down
1 change: 0 additions & 1 deletion app/dapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
<link rel="stylesheet" href="./index.styl" type="text/css" />
</head>
<body>
<div class="frameOverlay"></div>
<div id="dapp"></div>
<script type="module" src="./index.js" nonce="2f0d956b-0d9c-4f1e-874a-57a1ec828872"></script>
</body>
Expand Down
1 change: 0 additions & 1 deletion app/dapp/index.styl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ body
right 0px
bottom 0px
z-index 0
background var(--ghostZ)

.mainLeft
position absolute
Expand Down
138 changes: 103 additions & 35 deletions main/dapps/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
import path from 'path'
import { Readable } from 'stream'
import { hash } from 'eth-ens-namehash'
import log from 'electron-log'
import crypto from 'crypto'
import tar from 'tar-fs'

import store from '../store'
import nebulaApi from '../nebula'
import server from './server'
import extractColors from '../windows/extractColors'
import { dappPathExists, getDappCacheDir, isDappVerified } from './verify'

const nebula = nebulaApi()

class DappStream extends Readable {
constructor(hash: string) {
super()
this.start(hash)
}
async start(hash: string) {
for await (const buf of nebula.ipfs.get(hash, { archive: true })) {
this.push(buf)
}
this.push(null)
}
_read() {}
}

function getDapp(dappId: string): Dapp {
return store('main.dapps', dappId)
}
Expand All @@ -28,30 +46,87 @@ async function getDappColors(dappId: string) {
}
}

const createTarStream = (dappId: string) => {
return tar.extract(getDappCacheDir(), {
map: (header) => ({ ...header, name: path.join(dappId, ...header.name.split('/').slice(1)) })
})
}

const writeDapp = async (dappId: string, hash: string) => {
return new Promise<void>((resolve, reject) => {
try {
const dapp = new DappStream(hash)
const tarStream = createTarStream(dappId)

tarStream.on('error', reject)
tarStream.on('finish', resolve)

dapp.pipe(tarStream)
} catch (e) {
reject(e)
}
})
}

const cacheDapp = async (dappId: string, hash: string) => {
await writeDapp(dappId, hash)
await getDappColors(dappId)

return dappId
}

// TODO: change to correct manifest type one Nebula version with types are published
async function updateDappContent(dappId: string, contentURI: string, manifest: any) {
// TODO: Make sure content is pinned before proceeding
store.updateDapp(dappId, { content: contentURI, manifest })
async function updateDappContent(dappId: string, manifest: any) {
try {
// Create a local cache of the content
await cacheDapp(dappId, manifest.content)
store.updateDapp(dappId, { content: manifest.content, manifest })
} catch (e) {
log.error('error updating dapp cache', e)
}
}

let retryTimer: NodeJS.Timeout

// Takes dappId and checks if the dapp is up to date
async function checkStatus(dappId: string) {
clearTimeout(retryTimer)
const dapp = store('main.dapps', dappId)
const dapp = store('main.dapps', dappId) as Dapp

try {
const resolved = await nebula.resolve(dapp.ens)
const { record, manifest } = await nebula.resolve(dapp.ens)
const { version, content } = manifest || {}

if (!content) {
log.error(
`Attempted load dapp with id ${dappId} (${dapp.ens}) but manifest contained no content`,
manifest
)
return
}

const version = (resolved.manifest || {}).version || 'unknown'
log.info(`Resolved content for ${dapp.ens}, version: ${version || 'unknown'}`)

log.info(`resolved content for ${dapp.ens}, version: ${version}`)
store.updateDapp(dappId, { record })

store.updateDapp(dappId, { record: resolved.record })
if (dapp.content !== resolved.record.content) {
updateDappContent(dappId, resolved.record.content, resolved.manifest)
const isDappCurrent = async () => {
return (
dapp.content === content && (await dappPathExists(dappId)) && (await isDappVerified(dappId, content))
)
}

if (!dapp.colors) getDappColors(dappId)
// Checks if all assets are up to date with current manifest
if (!(await isDappCurrent())) {
log.info(`Updating content for dapp ${dappId} from hash ${content}`)
// Sets status to 'updating' when updating the bundle
store.updateDapp(dappId, { status: 'updating' })
// Update dapp assets
await updateDappContent(dappId, manifest)
} else {
log.info(`Dapp ${dapp.ens} already up to date: ${content}`)
}

// Sets status to 'ready' when done
store.updateDapp(dappId, { status: 'ready' })

// The frame id 'dappLauncher' needs to refrence target frame
Expand All @@ -67,35 +142,33 @@ async function checkStatus(dappId: string) {
store.updateDapp(dappId, { status: 'failed', checkStatusRetryCount: 0 })
}
}

// Takes dapp entry and config
// Checks if assets are correctly synced
// Checks if all assets are up to date with current manifest
// Installs new assets if changed and config is set to sync
// Sets status to 'updating' when updating the bundle
// Sets status to 'ready' when done

// dapp.config // the user's prefrences for installing assets from the manifest
// dapp.manifest // a copy of the latest manifest we have resolved for the dapp
// dapp.meta // meta info about the dapp including name, colors, icons, descriptions,
// dapp.ens // ens name for this dapp
// dapp.storage // local storage values for dapp
}

store.observer(() => {
const refreshDapps = ({ statusFilter = '' } = {}) => {
const dapps = store('main.dapps')

Object.keys(dapps || {})
.filter((id) => dapps[id].status === 'initial')
.filter((id) => !statusFilter || dapps[id].status === statusFilter)
.forEach((id) => {
store.updateDapp(id, { status: 'loading' })

if (nebula.ready()) {
checkStatus(id)
} else {
nebula.once('ready', () => checkStatus(id))
}
})
})
}

const checkNewDapps = () => refreshDapps({ statusFilter: 'initial' })

// Check all dapps on startup
refreshDapps()

// Check all dapps every hour
setInterval(() => refreshDapps(), 1000 * 60 * 60)

// Check any new dapps that are added
store.observer(checkNewDapps)

let nextId = 0
const getId = () => (++nextId).toString()
Expand All @@ -110,14 +183,10 @@ const surface = {
const id = hash(ens)
const status = 'initial'

// Validate ens name and config

// Check that dapp has not been added already
// If ens name has been installed
// return error
const existingDapp = store('main.dapps', id)

// If ens name has not been installed, start install
store.appDapp({ id, ens, status, config, manifest: {}, current: {} })
if (!existingDapp) store.appDapp({ id, ens, status, config, manifest: {}, current: {} })
},
addServerSession(namehash: string /* , session */) {
// server.sessions.add(namehash, session)
Expand All @@ -142,7 +211,6 @@ const surface = {
}

server.sessions.add(ens, session)

store.addFrameView(frameId, view)
} else {
store.updateDapp(dappId, { ens, status: 'initial', openWhenReady: true })
Expand Down
Loading

0 comments on commit d27140e

Please sign in to comment.