Skip to content

Commit

Permalink
Support manifest version 3 (#14)
Browse files Browse the repository at this point in the history
* Adapt to adapter API changes introduced by #4192

* Modify readme to reflect newly required config boilerplate

* Make manifest_version 3 default with option to specify

* Document adapter-browser-extension options 

Document options that can be passed to the adapter, including the newly introduced option to pass a  manifestVersion.

* Modify CSP as only externalized scripts are allowed in MV3

* Introduce helper function to externalize script content

The content security policy of manifest_version 3 does not allow for inlined scripts. Until kit implements a config option ( sveltejs/kit#1776 ) to externalize scripts, these changes should do for a quick and dirty externalization of the scripts' contents
  • Loading branch information
Theo-Steiner authored Jun 16, 2022
1 parent a377dd8 commit 1b76d89
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 10 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ import adapter from 'sveltekit-adapter-browser-extension';

export default {
kit: {
adapter: adapter(),
adapter: adapter({
// default options are shown
pages: 'build', // the output directory for the pages of your extension
assets: undefined, // the asset output directory is derived from pages if not specified explicitly
fallback: undefined, // set to true to output an SPA-like extension
manifestVersion: 3 // the version of the automatically generated manifest (Version 3 is required by Chrome).
}),
appDir: 'ext', // This is important - chrome extensions can't handle the default _app directory name.
prerender: {
default: true
Expand Down
59 changes: 50 additions & 9 deletions adapter-browser-extension.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { join } from 'path'
import { writeFileSync, readFileSync, existsSync } from 'fs'
import glob from 'tiny-glob'
import sjcl from 'sjcl'
import cheerio from 'cheerio'
import { applyToDefaults } from '@hapi/hoek'
Expand All @@ -21,16 +22,32 @@ function generate_csp (html) {
return `script-src 'self' ${csp_hashes}; object-src 'self'`
}

function generate_manifest (html) {
function generate_manifest (html, manifest_version) {
const project_placeholders = {
name: 'TODO',
version: '0.1'
}
if (manifest_version === 2) {
return {
manifest_version: 2,
browser_action: {
default_title: 'SvelteKit',
default_popup: 'index.html'
},
content_security_policy: generate_csp(html),
...project_placeholders
}
}
return {
browser_action: {
manifest_version: 3,
action: {
default_title: 'SvelteKit',
default_popup: 'index.html'
},
content_security_policy: generate_csp(html),
manifest_version: 2,
name: 'TODO',
version: '0.1'
content_security_policy: {
"extension_pages": "script-src 'self'; object-src 'self'"
},
...project_placeholders
}
}

Expand All @@ -42,9 +59,21 @@ function load_manifest () {
return {}
}

// Quick and dirty helper function to externalize scripts. Will become obsolete once kit provides a config option to do this ahead of time.
function externalizeScript(html, assets) {
return html.replace(
/<script type="module" data-hydrate="([\s\S]+)">([\s\S]+)<\/script>/,
(match, hydrationTarget, content) => {
const hash = Buffer.from(hash_script(content), 'base64').toString('hex');
const externalized_script_path = join(assets, `${hash}.js`);
writeFileSync(externalized_script_path, content);
return `<script type="module" data-hydrate="${hydrationTarget}" src="${hash}.js"></script>`;
}
);
}

/** @type {import('.')} */
export default function ({ pages = 'build', assets = pages, fallback } = {}) {
export default function ({ pages = 'build', assets = pages, fallback, manifestVersion = 3 } = {}) {
return {
name: 'sveltekit-adapter-browser-extension',

Expand All @@ -68,8 +97,20 @@ export default function ({ pages = 'build', assets = pages, fallback } = {}) {

const index_page = join(assets, 'index.html')
const index = readFileSync(index_page)

const generated_manifest = generate_manifest(index.toString())

/** The content security policy of manifest_version 3 does not allow for inlined scripts.
Until kit implements a config option (#1776) to externalize scripts, the below code block should do
for a quick and dirty externalization of the scripts' contents **/
if (manifestVersion === 3) {
const HTML_files = await glob('**/*.html', { cwd: pages, dot: true, absolute: true, filesOnly: true })
HTML_files.forEach(path => {
let html = readFileSync(path, {encoding:'utf8'})
html = externalizeScript(html, assets)
writeFileSync(path, html)
});
}

const generated_manifest = generate_manifest(index.toString(), manifestVersion)
const merged_manifest = applyToDefaults(generated_manifest, provided_manifest, { nullOverride: true })

writeFileSync(join(assets, manifest_filename), JSON.stringify(merged_manifest))
Expand Down

0 comments on commit 1b76d89

Please sign in to comment.