Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dapp caching #1406

Merged
merged 16 commits into from
Feb 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
floating marked this conversation as resolved.
Show resolved Hide resolved
<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