-
Notifications
You must be signed in to change notification settings - Fork 2
/
adapter.ts
120 lines (100 loc) · 3.62 KB
/
adapter.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import type { Builder, Adapter } from '@sveltejs/kit'
import { writeFileSync, readFileSync, existsSync } from 'fs'
import { join } from 'path'
import { applyToDefaults } from '@hapi/hoek'
import * as cheerio from 'cheerio'
import glob from 'tiny-glob'
import sjcl from 'sjcl'
const manifest_filename = 'manifest.json'
function hash_script(s: string) {
const hashed = sjcl.hash.sha256.hash(s)
return sjcl.codec.base64.fromBits(hashed)
}
function generate_csp(html: string) {
const $ = cheerio.load(html)
const csp_hashes = $('script[type="module"]')
// @ts-ignore
.map((i, el) => hash_script($(el).get()[0].children[0].data))
.toArray()
.map((h) => `'sha256-${h}'`)
.join(' ')
return `script-src 'self' ${csp_hashes}; object-src 'self'`
}
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 {
manifest_version: 3,
action: {
default_title: 'SvelteKit',
default_popup: 'index.html',
},
content_security_policy: {
extension_pages: "script-src 'self'; object-src 'self'",
},
...project_placeholders,
}
}
function load_manifest() {
if (existsSync(manifest_filename)) {
return JSON.parse(readFileSync(manifest_filename, 'utf-8'))
}
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: string, assets: string) {
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>`
},
)
}
export default function ({ pages = 'build', assets = pages, fallback = undefined, manifestVersion = 3 } = {}): Adapter {
return {
name: 'sveltekit-adapter-browser-extension',
async adapt(builder: Builder) {
if (!fallback && !builder.config.kit.prerender.default) {
builder.log.warn('You should set `config.kit.prerender.default` to `true` if no fallback is specified')
}
const provided_manifest = load_manifest()
builder.rimraf(assets)
builder.rimraf(pages)
builder.writeClient(assets)
builder.writePrerendered(pages, { fallback })
const index_page = join(assets, 'index.html')
const index = readFileSync(index_page)
/** 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))
builder.rimraf(join(assets, '_app'))
},
}
}