Skip to content

Commit

Permalink
Add ability to load a ?hubURL= from URL bar (#4745)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin authored Jan 7, 2025
1 parent bad8184 commit e150c03
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 217 deletions.
46 changes: 25 additions & 21 deletions packages/web-core/src/SessionConnections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,40 +32,44 @@ export function WebSessionConnectionsMixin(pluginManager: PluginManager) {
const superDeleteConnection = self.deleteConnection
const superAddConnectionConf = self.addConnectionConf
return {
/**
* #action
*/
addConnectionConf(connectionConf: BaseConnectionConfigModel) {
if (self.adminMode) {
return superAddConnectionConf(connectionConf)
} else {
const { connectionId, type } = connectionConf
if (!type) {
throw new Error(`unknown connection type ${type}`)
}
const connection = self.sessionTracks.find(
c => c.connectionId === connectionId,
)
if (connection) {
return connection
} else {
const length = self.sessionConnections.push(connectionConf)
return self.sessionConnections[length - 1]
}
}
const { connectionId, type } = connectionConf
if (!type) {
throw new Error(`unknown connection type ${type}`)
}
const connection = self.sessionTracks.find(
c => c.connectionId === connectionId,
)
if (connection) {
return connection
}
const length = self.sessionConnections.push(connectionConf)
return self.sessionConnections[length - 1]
},

/**
* #action
*/
deleteConnection(configuration: AnyConfigurationModel) {
let deletedConn: unknown
if (self.adminMode) {
deletedConn = superDeleteConnection(configuration)
}
if (!deletedConn) {
return superDeleteConnection(configuration)
} else {
const { connectionId } = configuration
const idx = self.sessionConnections.findIndex(
c => c.connectionId === connectionId,
)
if (idx === -1) {
return undefined
}
return self.sessionConnections.splice(idx, 1)
return idx === -1
? undefined
: self.sessionConnections.splice(idx, 1)
}
return deletedConn
},
}
})
Expand Down
117 changes: 75 additions & 42 deletions products/jbrowse-web/src/SessionLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,10 @@ import { addDisposer, types } from 'mobx-state-tree'
import { readSessionFromDynamo } from './sessionSharing'
import { addRelativeUris, checkPlugins, fromUrlSafeB64, readConf } from './util'

import type { SessionTriagedInfo } from './types'
import type { PluginDefinition, PluginRecord } from '@jbrowse/core/PluginLoader'
import type { Instance } from 'mobx-state-tree'

export interface SessionTriagedInfo {
snap: unknown
origin: string
reason: PluginDefinition[]
}

const SessionLoader = types
.model({
/**
Expand Down Expand Up @@ -66,6 +61,11 @@ const SessionLoader = types
* #property
*/
initialTimestamp: types.number,

/**
* #property
*/
hubURL: types.maybe(types.array(types.string)),
})
.volatile(() => ({
/**
Expand All @@ -84,6 +84,10 @@ const SessionLoader = types
* #volatile
*/
sessionSpec: undefined as Record<string, unknown> | undefined,
/**
* #volatile
*/
hubSpec: undefined as Record<string, unknown> | undefined,
/**
* #volatile
*/
Expand Down Expand Up @@ -134,6 +138,12 @@ const SessionLoader = types
get isSpecSession() {
return !!self.sessionQuery?.startsWith('spec-')
},
/**
* #getter
*/
get isHubSession() {
return !!self.hubURL
},
/**
* #getter
*/
Expand Down Expand Up @@ -275,8 +285,7 @@ const SessionLoader = types
try {
const pluginLoader = new PluginLoader(snap.sessionPlugins || [], {
fetchESM: url => import(/* webpackIgnore:true */ url),
})
pluginLoader.installGlobalReExports(window)
}).installGlobalReExports(window)
const plugins = await pluginLoader.load(window.location.href)
self.setSessionPlugins([...plugins])
} catch (e) {
Expand Down Expand Up @@ -315,38 +324,38 @@ const SessionLoader = types
*/
async fetchConfig() {
// @ts-expect-error
const path = window.__jbrowseConfigPath
const { hubURL, configPath = path || 'config.json' } = self
if (!hubURL) {
const text = await openLocation({
uri:
configPath +
// @ts-expect-error
(window.__jbrowseCacheBuster ? `?rand=${Math.random()}` : ''),
locationType: 'UriLocation',
}).readFile('utf8')
const config = JSON.parse(text)
const configUri = new URL(configPath, window.location.href)
addRelativeUris(config, configUri)

let { configPath = window.__jbrowseConfigPath || 'config.json' } = self

// @ts-expect-error

if (window.__jbrowseCacheBuster) {
configPath += `?rand=${Math.random()}`
}

const text = await openLocation({
uri: configPath,
locationType: 'UriLocation',
}).readFile('utf8')
const config = JSON.parse(text)
const configUri = new URL(configPath, window.location.href)
addRelativeUris(config, configUri)

// cross origin config check
if (configUri.hostname !== window.location.hostname) {
const configPlugins = config.plugins || []
const configPluginsAllowed = await checkPlugins(configPlugins)
if (!configPluginsAllowed) {
self.setSessionTriaged({
snap: config,
origin: 'config',
reason: configPlugins,
})
return
// cross origin config check
if (configUri.hostname !== window.location.hostname) {
const configPlugins = config.plugins || []
const configPluginsAllowed = await checkPlugins(configPlugins)
if (!configPluginsAllowed) {
self.setSessionTriaged({
snap: config,
origin: 'config',
reason: configPlugins,
})
return
}
}
await this.fetchPlugins(config)
self.setConfigSnapshot(config)
} else {
self.setConfigSnapshot({})
}
await this.fetchPlugins(config)
self.setConfigSnapshot(config)
},
/**
* #action
Expand Down Expand Up @@ -399,7 +408,10 @@ const SessionLoader = types
)

const session = JSON.parse(await fromUrlSafeB64(decryptedSession))
await this.setSessionSnapshot({ ...session, id: nanoid() })
await this.setSessionSnapshot({
...session,
id: nanoid(),
})
},
/**
* #action
Expand All @@ -409,7 +421,10 @@ const SessionLoader = types
// @ts-expect-error
await fromUrlSafeB64(self.sessionQuery.replace('encoded-', '')),
)
await this.setSessionSnapshot({ ...session, id: nanoid() })
await this.setSessionSnapshot({
...session,
id: nanoid(),
})
},
/**
* #action
Expand Down Expand Up @@ -451,13 +466,28 @@ const SessionLoader = types
}
}
},

/**
* #action
*/
decodeHubSpec() {
const { hubURL, sessionTracksParsed: sessionTracks } = self

self.hubSpec = {
sessionTracks,
hubURL,
}
},
/**
* #action
*/
async decodeJsonUrlSession() {
// @ts-expect-error
const session = JSON.parse(self.sessionQuery.replace('json-', ''))
await this.setSessionSnapshot({ ...session.session, id: nanoid() })
const { session } = JSON.parse(self.sessionQuery.replace(/^json-/, ''))
await this.setSessionSnapshot({
...session,
id: nanoid(),
})
},
/**
* #aftercreate
Expand All @@ -479,6 +509,7 @@ const SessionLoader = types
isSharedSession,
isJsonSession,
isJb1StyleSession,
isHubSession,
sessionQuery,
configSnapshot,
} = self
Expand All @@ -505,6 +536,9 @@ const SessionLoader = types
this.decodeJb1StyleSession()
} else if (isEncodedSession) {
await this.decodeEncodedUrlSession()
} else if (isHubSession) {
this.decodeHubSpec()
self.setBlankSession(true)
} else if (isJsonSession) {
await this.decodeJsonUrlSession()
} else if (isLocalSession) {
Expand All @@ -525,7 +559,6 @@ const SessionLoader = types
} catch (e) {
console.error(e)
self.setConfigError(e)
return
}
})()
},
Expand Down
31 changes: 1 addition & 30 deletions products/jbrowse-web/src/components/ConfigWarningDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { pluginDescriptionString } from '@jbrowse/core/PluginLoader'
import { Dialog } from '@jbrowse/core/ui'
import { nanoid } from '@jbrowse/core/util/nanoid'
import WarningIcon from '@mui/icons-material/Warning'
import {
Button,
Expand All @@ -9,12 +8,9 @@ import {
DialogContentText,
} from '@mui/material'

import factoryReset from '../factoryReset'

import type { SessionLoaderModel } from '../SessionLoader'
import type { PluginDefinition } from '@jbrowse/core/PluginLoader'

function ConfigWarningDialog({
export default function ConfigWarningDialog({
onConfirm,
onCancel,
reason,
Expand Down Expand Up @@ -61,28 +57,3 @@ function ConfigWarningDialog({
</Dialog>
)
}

export default function ConfigTriaged({
loader,
handleClose,
}: {
loader: SessionLoaderModel
handleClose: () => void
}) {
const { sessionTriaged } = loader
return sessionTriaged ? (
<ConfigWarningDialog
onConfirm={async () => {
const session = JSON.parse(JSON.stringify(sessionTriaged.snap))
await loader.fetchPlugins(session)
loader.setConfigSnapshot({ ...session, id: nanoid() })
handleClose()
}}
onCancel={async () => {
await factoryReset()
handleClose()
}}
reason={sessionTriaged.reason}
/>
) : null
}
Loading

0 comments on commit e150c03

Please sign in to comment.