Repository navigation
store: honor snap confinement on install #133
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
Changes from all commits
f3a733b
11d006e
3e8beed
6b9d005
3775c1b
fa663b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import type { StoreApp } from './types'; | ||
|
|
||
| export function installConfinement(value?: string) { | ||
| const confinement = value?.trim().toLowerCase(); | ||
| return confinement === 'classic' || confinement === 'devmode' || confinement === 'strict' | ||
| ? confinement | ||
| : undefined; | ||
| } | ||
|
|
||
| function installFlag(confinement?: string) { | ||
| const mode = installConfinement(confinement); | ||
| return mode === 'classic' ? '--classic' : mode === 'devmode' ? '--devmode' : ''; | ||
| } | ||
|
|
||
| function commandFor(name: string, confinement?: string) { | ||
| const flag = installFlag(confinement); | ||
| return `sudo snap install ${name}${flag ? ` ${flag}` : ''}`; | ||
| } | ||
|
|
||
| const unamePatterns: Record<string, string> = { | ||
| amd64: 'x86_64', | ||
| arm64: 'aarch64|arm64', | ||
| armhf: 'armv7l|armv7*', | ||
| i386: 'i386|i486|i586|i686', | ||
| powerpc: 'ppc|powerpc', | ||
| ppc64el: 'ppc64le', | ||
| riscv64: 'riscv64', | ||
| s390x: 's390x', | ||
| }; | ||
|
|
||
| export function snapInstallCommand(app: Pick<StoreApp, 'name' | 'confinement' | 'confinementByArchitecture'>) { | ||
| const modes = Object.entries(app.confinementByArchitecture || {}) | ||
| .map(([architecture, value]) => [architecture, installConfinement(value)] as const) | ||
| .filter((entry): entry is readonly [string, 'strict' | 'classic' | 'devmode'] => Boolean(entry[1])); | ||
| if (!modes.length) return commandFor(app.name, app.confinement); | ||
|
|
||
| const uniqueModes = new Set(modes.map(([, mode]) => mode)); | ||
| if (uniqueModes.size === 1) return commandFor(app.name, modes[0][1]); | ||
|
|
||
| const cases = modes.flatMap(([architecture, mode]) => { | ||
| const pattern = unamePatterns[architecture]; | ||
| return pattern ? [`${pattern}) ${commandFor(app.name, mode)} ;;`] : []; | ||
| }); | ||
| if (!cases.length) return commandFor(app.name, app.confinement); | ||
| return `case "$(uname -m)" in ${cases.join(' ')} *) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;; esac`; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,7 +56,7 @@ function publicCacheKey(request: Request, env: Env) { | |
| const key = new URL(source.origin + source.pathname); | ||
| if (source.pathname === '/api/storefront' || source.pathname === '/api/catalog') { | ||
| key.searchParams.set('version', safeVersion(source.searchParams.get('version'), env)); | ||
| if (source.pathname === '/api/catalog') key.searchParams.set('schema', 'v3'); | ||
| if (source.pathname === '/api/catalog') key.searchParams.set('schema', 'v4'); | ||
| } else if (source.pathname === '/api/search') { | ||
| key.searchParams.set('version', safeVersion(source.searchParams.get('version'), env)); | ||
| key.searchParams.set('q', (source.searchParams.get('q') || '').trim().toLowerCase()); | ||
|
|
@@ -75,7 +75,7 @@ function publicCacheResponse(response: Response, status: 'HIT' | 'MISS', browser | |
| } | ||
|
|
||
| async function edgeCached(request: Request, env: Env, ctx: ExecutionContext, edgeTtl: number, browserTtl: number, loader: () => Promise<Response>) { | ||
| const cache = await caches.open('capos-snap-public-v2'); | ||
| const cache = await caches.open('capos-snap-public-v4'); | ||
| const key = publicCacheKey(request, env); | ||
| const cached = await cache.match(key); | ||
| if (cached) return publicCacheResponse(cached, 'HIT', browserTtl); | ||
|
|
@@ -181,6 +181,7 @@ function canonicalApp(result: Record<string, any>, includeRich = false) { | |
| featured: categories.some((c: any) => c.featured), | ||
| version: revision.version || '—', | ||
| channel: revision.channel || 'stable', | ||
| confinement: revision.confinement || undefined, | ||
| architectures: ['amd64', 'arm64'], | ||
| webdesktop: 'unknown', | ||
| updated: 'Upstream', | ||
|
|
@@ -206,7 +207,7 @@ function canonicalApp(result: Record<string, any>, includeRich = false) { | |
| }; | ||
| } | ||
|
|
||
| const CANONICAL_LIST_FIELDS = 'title,summary,publisher,version,media,categories,channel,revision'; | ||
| const CANONICAL_LIST_FIELDS = 'title,summary,publisher,version,media,categories,channel,revision,confinement'; | ||
|
fwerkor marked this conversation as resolved.
|
||
|
|
||
| async function canonicalFind(base: string, query: URLSearchParams, cacheTtl = 120) { | ||
| const params = new URLSearchParams(query); params.set('fields',CANONICAL_LIST_FIELDS); | ||
|
|
@@ -223,6 +224,9 @@ async function canonicalInfo(base: string, name: string, cacheTtl = 3600) { | |
| const payload = await response.json<Record<string,any>>(); | ||
| const channelMap = Array.isArray(payload['channel-map']) ? payload['channel-map'] : []; | ||
| const preferred = channelMap.find((entry:any) => entry.channel?.architecture === 'amd64' && entry.channel?.risk === 'stable') || channelMap[0] || {}; | ||
| const confinementByArchitecture = Object.fromEntries(channelMap | ||
| .filter((entry:any) => entry.channel?.name === 'stable' && ['strict','classic','devmode'].includes(entry.confinement)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Canonical's v2 info response identifies stable channels with full names such as Useful? React with 👍 / 👎. |
||
| .map((entry:any) => [entry.channel.architecture, entry.confinement])); | ||
| const app = canonicalApp({ | ||
| 'snap-id': payload['snap-id'], | ||
| name: payload.name || name, | ||
|
|
@@ -234,8 +238,11 @@ async function canonicalInfo(base: string, name: string, cacheTtl = 3600) { | |
| releasedAt: preferred.channel?.['released-at'] || preferred['created-at'], | ||
| }, | ||
| }, true); | ||
| app.architectures = [...new Set(channelMap.map((entry:any) => entry.channel?.architecture).filter(Boolean))] as string[]; | ||
| return app; | ||
| return { | ||
| ...app, | ||
| architectures: [...new Set(channelMap.map((entry:any) => entry.channel?.architecture).filter(Boolean))] as string[], | ||
| confinementByArchitecture, | ||
| }; | ||
| } | ||
|
|
||
| const CANONICAL_CATALOG_CATEGORIES = [ | ||
|
|
@@ -244,7 +251,7 @@ const CANONICAL_CATALOG_CATEGORIES = [ | |
| 'news-and-weather', 'personalisation', 'photo-and-video', 'productivity', 'science', | ||
| 'security', 'server-and-cloud', 'social', 'utilities' | ||
| ]; | ||
| const CATALOG_SNAPSHOT_KEY = '_cache/canonical-catalog-v3.json'; | ||
| const CATALOG_SNAPSHOT_KEY = '_cache/canonical-catalog-v4.json'; | ||
|
fwerkor marked this conversation as resolved.
|
||
| const CATALOG_SNAPSHOT_TTL = 6 * 3600; | ||
| let catalogRefreshPromise: Promise<void> | null = null; | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.