Skip to content

Commit

Permalink
feat: external objects with URL and metadata check (#489)
Browse files Browse the repository at this point in the history
  • Loading branch information
filoozom authored Nov 14, 2023
1 parent 7108b07 commit 143ee16
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 12 deletions.
5 changes: 3 additions & 2 deletions src/lib/objects/external/iframe.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import template from './template.html?raw'
// Types
import { getNPMObject, type LoadedObject } from './lib'
import { getObject, type LoadedObject } from './lib'
import { makeIframeDispatcher, type IframeContextChange } from './dispatch'
import { postWindowMessage, registerWindow, unregisterWindow } from '.'
import { onDestroy } from 'svelte'
Expand All @@ -22,6 +22,7 @@
return template
.replace('__CSP__', object.csp)
.replace('__URL__', object.script)
.replace('__URL_INTEGRITY__', object.scriptIntegrity)
.replace('__CLASS__', object.className)
}
Expand Down Expand Up @@ -75,7 +76,7 @@
}
$: args &&
getNPMObject(args.objectId, message ? 'chat' : 'standalone').then((result) => (object = result))
getObject(args.objectId, message ? 'chat' : 'standalone').then((result) => (object = result))
$: if (iframe && iframe.contentWindow) {
registerWindow(args.instanceId, iframe.contentWindow)
updateContext()
Expand Down
83 changes: 76 additions & 7 deletions src/lib/objects/external/lib.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { IPFS_GATEWAY } from '$lib/adapters/ipfs'

// Not sure how inefficient this is
const scripts = import.meta.glob('/node_modules/**/object/index.js', {
as: 'url',
Expand All @@ -14,14 +16,30 @@ export type Csp = {
'base-uri'?: string
}

export type WakuScriptType = 'chat' | 'standalone'

export type WakuFile = {
path: string
hash: `sha256-${string}`
}

export type WakuFiles = {
logo: WakuFile
chat: WakuFile
standalone?: WakuFile
}

export type WakuObject = {
name: string
description: string
standalone?: boolean
csp: Csp
files: WakuFiles
}

export type LoadedObject = {
script: string
scriptIntegrity: `sha256-${string}`
csp: string
name: string
className: string
Expand All @@ -41,25 +59,76 @@ const formatCsp = (csp: Csp, add?: Csp): string => {
.join('; ')
}

export const getNPMObject = async (
module: string,
className: 'chat' | 'standalone',
export const getNPMObject = async (module: string, type: WakuScriptType) => {
const object = (await objects[`/node_modules/${module}/object/metadata.json`]()) as WakuObject
const file = object.files[type]

if (!file) {
throw new Error(`object does not include ${type} script`)
}

const script = await scripts[`/node_modules/${module}/object/${file.path}`]()
return { object, script, integrity: file.hash }
}

export const getURLObject = async (url: string, type: WakuScriptType) => {
const object = (await (await fetch(`${url}/metadata.json`)).json()) as WakuObject
const file = object.files[type]

if (!file) {
throw new Error(`object does not include ${type} script`)
}

// Prepend the URL to all files
for (const [type, { path }] of Object.entries(object.files)) {
// @ts-expect-error TODO: Fix this
object.files[type as keyof WakuFiles].path = `${url}/${path}`
}

const { path: script, hash: integrity } = file
return { object, script, integrity }
}

export const getIPFSObject = async (cid: string, type: WakuScriptType) => {
const { object, script, integrity } = await getURLObject(`${IPFS_GATEWAY}/${cid}`, type)

// TODO: Validate metadata.json hash

return { object, script, integrity }
}

// TODO: Figure out the ID part
export const getObjectSpec = async (objectId: string, type: WakuScriptType) => {
if (objectId.startsWith('url:')) {
return getURLObject(objectId.substring(4), type)
}

if (objectId.startsWith('ipfs:')) {
return getIPFSObject(objectId.substring(5), type)
}

return getNPMObject(objectId, type)
}

export const getObject = async (
objectId: string,
type: WakuScriptType,
): Promise<LoadedObject | null> => {
try {
const object = (await objects[`/node_modules/${module}/object/metadata.json`]()) as WakuObject
const script = await scripts[`/node_modules/${module}/object/index.js`]()
const { object, script, integrity } = await getObjectSpec(objectId, type)

const added = { ...DEFAULT_CSP } as Csp
if (!added['script-src']) {
added['script-src'] = ''
}
added['script-src'] += ` ${script}`
added['script-src'] += ` '${integrity}'`

return {
script,
scriptIntegrity: integrity,
csp: formatCsp(object.csp, added),
name: object.name,
className,
className: type,
}
} catch (err) {
// TODO: shouldn't this throw?
Expand Down
4 changes: 2 additions & 2 deletions src/lib/objects/external/standalone.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
import type { WakuObjectArgs } from '..'
import IframeComponent from './iframe.svelte'
import { getNPMObject, type LoadedObject } from './lib'
import { getObject, type LoadedObject } from './lib'
// Exports
export let args: WakuObjectArgs
let object: LoadedObject | null
$: args && getNPMObject(args.objectId, 'standalone').then((result) => (object = result))
$: args && getObject(args.objectId, 'standalone').then((result) => (object = result))
// Utility function which makes it easier to handle history for closing the object
const exitObject = (depth: number) => () =>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/objects/external/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</head>
<body>
<div id="app" class="__CLASS__"></div>
<script type="module" src="__URL__"></script>
<script type="module" src="__URL__" integrity="__URL_INTEGRITY__"></script>
<!-- prettier-ignore -->
<script>
const postSize = () => {
Expand Down
21 changes: 21 additions & 0 deletions src/routes/identity/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@
import { uploadPicture } from '$lib/adapters/ipfs'
import Avatar from '$lib/components/avatar.svelte'
import { errorStore } from '$lib/stores/error'
import { installedObjectStore } from '$lib/stores/installed-objects'
import { getObjectSpec } from '$lib/objects/external/lib'
let avatar = $profile.avatar
let name = $profile.name
let objectPath = ''
$: if ($profile.loading === false && !name && !avatar) {
name = $profile.name
Expand Down Expand Up @@ -94,6 +97,20 @@
}, 1000)
}
async function addObject() {
const { object } = await getObjectSpec(objectPath, 'chat')
installedObjectStore.update((state) => {
state.objects.set(objectPath, {
objectId: objectPath,
name: object.name,
description: object.description,
logo: object.files.logo.path,
})
return { ...state }
})
objectPath = ''
}
onDestroy(() => {
if (timer) {
clearTimeout(timer)
Expand Down Expand Up @@ -178,6 +195,10 @@
Disconnect identity from device
</Button>
</Container>
<Container align="center" gap={12} padX={24} padY={24}>
<InputField bind:value={objectPath} label="Object path" />
<Button on:click={addObject}>Add object</Button>
</Container>
<Spacer height={12} />
</AuthenticatedOnly>
</Layout>
Expand Down

1 comment on commit 143ee16

@vercel
Copy link

@vercel vercel bot commented on 143ee16 Nov 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.