| | import { webpack, sources } from 'next/dist/compiled/webpack/webpack' |
| | import crypto from 'crypto' |
| | import { SUBRESOURCE_INTEGRITY_MANIFEST } from '../../../shared/lib/constants' |
| |
|
| | const PLUGIN_NAME = 'SubresourceIntegrityPlugin' |
| |
|
| | export type SubresourceIntegrityAlgorithm = 'sha256' | 'sha384' | 'sha512' |
| |
|
| | export class SubresourceIntegrityPlugin { |
| | constructor(private readonly algorithm: SubresourceIntegrityAlgorithm) {} |
| |
|
| | public apply(compiler: webpack.Compiler) { |
| | compiler.hooks.make.tap(PLUGIN_NAME, (compilation) => { |
| | compilation.hooks.afterProcessAssets.tap( |
| | { |
| | name: PLUGIN_NAME, |
| | stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, |
| | }, |
| | () => { |
| | |
| | let files = new Set<string>() |
| | for (const asset of compilation.getAssets()) { |
| | files.add(asset.name) |
| | } |
| |
|
| | |
| | const hashes: Record<string, string> = {} |
| | for (const file of files.values()) { |
| | |
| | const asset = compilation.getAsset(file) |
| | if (!asset) { |
| | throw new Error(`could not get asset: ${file}`) |
| | } |
| |
|
| | |
| | const buffer = asset.source.buffer() |
| |
|
| | |
| | const hash = crypto |
| | .createHash(this.algorithm) |
| | .update(buffer) |
| | .digest() |
| | .toString('base64') |
| |
|
| | hashes[file] = `${this.algorithm}-${hash}` |
| | } |
| |
|
| | const json = JSON.stringify(hashes, null, 2) |
| | const file = 'server/' + SUBRESOURCE_INTEGRITY_MANIFEST |
| | compilation.emitAsset( |
| | file + '.js', |
| | new sources.RawSource( |
| | `self.__SUBRESOURCE_INTEGRITY_MANIFEST=${JSON.stringify(json)}` |
| | |
| | |
| | ) as unknown as webpack.sources.RawSource |
| | ) |
| | compilation.emitAsset( |
| | file + '.json', |
| | new sources.RawSource( |
| | json |
| | |
| | |
| | ) as unknown as webpack.sources.RawSource |
| | ) |
| | } |
| | ) |
| | }) |
| | } |
| | } |
| |
|