I recently released a how-to article and video on deploying Blazor WASM to GitHub Pages. In this content I demonstrated how to deploy Blazor WASM to GitHub pages manually and automatically using GitHub Actions, but I did not have the PWA feature enabled. When you create a Blazor WASM application, you can pass a --pwa
flag to include PWA support.
It turns out that the PWA feature breaks in those how-to's. The service worker requests all the files with integrity checking, and in those how-to's the base-tag of the index.html file is modified. This modification causes the integrity check to fail because the pre-calculated hash stored in service-worker-assets.js for index.html mismatches. The result was the following error:
Service worker: Install
Failed to find a valid digest in the 'integrity' attribute for resource 'http://YOURURL/index.html' with computed SHA-256 integrity 'sYKnuzlqE606K8G3ejaHYO2arpP3AQOjtIDxiCzAKyA='. The resource has been blocked.
Unknown error occurred while trying to verify integrity.
service-worker.js:1 Uncaught (in promise) TypeError: Failed to fetch
This issue will surface when any modification is made to the files listed in the service-worker-assets.js file. The service-worker-assets.js file is generated during publish and any modification made to the listed files after publish will cause the integrity check to fail.
Solution
Microsoft's documentation has some guidance on how to detect and troubleshoot this issue. You have the option to disable the integrity checking altogether, but you will lose the safety guarantees offered by integrity checking.
Alternatively, you can calculate the new hash for all the modified files and update them in the service-worker-assets.js file. You can calculate the hash with with these PowerShell commands:
$Signature = Get-FileHash -Path "path/to/your/file" -Algorithm SHA256
$SignatureBytes = [byte[]] -split ($Signature.Hash -replace '..', '0x$& ')
$SignatureBase64 = [System.Convert]::ToBase64String($SignatureBytes)
$NewHash = "sha256-$SignatureBase64"
Write-Host $NewHash
Alternatively, you can use openssl to generate the hash in bash:
echo "sha256-$(openssl dgst -sha256 -binary \\path\\to\\your\\file | openssl base64 -A)"
Manually going over each file and updating the hash in the service-worker-assets.js file can be painful, so here's a PowerShell script and a Bash script that will iterate over every file listed in service-worker-assets.js.
If the hash is different, the hash is automatically updated in service-worker-assets.js:
PowerShell:
# make sure you're in the wwwroot folder of the published application
$JsFileContent = Get-Content -Path service-worker-assets.js -Raw
# remove JavaScript from contents so it can be interpreted as JSON
$Json = $JsFileContent.Replace("self.assetsManifest = ", "").Replace(";", "") | ConvertFrom-Json
# grab the assets JSON array
$Assets = $Json.assets
foreach ($Asset in $Assets) {
$OldHash = $Asset.hash
$Path = $Asset.url
$Signature = Get-FileHash -Path $Path -Algorithm SHA256
$SignatureBytes = [byte[]] -split ($Signature.Hash -replace '..', '0x$& ')
$SignatureBase64 = [System.Convert]::ToBase64String($SignatureBytes)
$NewHash = "sha256-$SignatureBase64"
If ($OldHash -ne $NewHash) {
Write-Host "Updating hash for $Path from $OldHash to $NewHash"
# slashes are escaped in the js-file, but PowerShell unescapes them automatically,
# we need to re-escape them
$OldHash = $OldHash.Replace("/", "\/")
$NewHash = $NewHash.Replace("/", "\/")
$JsFileContent = $JsFileContent.Replace("""$OldHash""", """$NewHash""")
}
}
Set-Content -Path service-worker-assets.js -Value $JsFileContent -NoNewline
Bash:
#!/bin/bash
# make sure you're in the wwwroot folder of the published application
jsFile=$(<service-worker-assets.js)
# remove JavaScript from contents so it can be interpreted as JSON
json=$(echo "$jsFile" | sed "s/self.assetsManifest = //g" | sed "s/;//g")
# grab the assets JSON array
assets=$(echo "$json" | jq '.assets[]' -c)
for asset in $assets
do
oldHash=$(echo "$asset" | jq '.hash')
#remove leading and trailing quotes
oldHash="${oldHash:1:-1}"
path=$(echo "$asset" | jq '.url')
#remove leading and trailing quotes
path="${path:1:-1}"
newHash="sha256-$(openssl dgst -sha256 -binary $path | openssl base64 -A)"
if [$oldHash != $newHash]; then
# escape slashes for json
oldHash=$(echo "$oldHash" | sed 's;/;\\/;g')
newHash=$(echo "$newHash" | sed 's;/;\\/;g')
echo "Updating hash for $path from $oldHash to $newHash"
# escape slashes second time for sed
oldHash=$(echo "$oldHash" | sed 's;/;\\/;g')
jsFile=$(echo -n "$jsFile" | sed "s;$oldHash;$newHash;g")
fi
done
echo -n "$jsFile" > service-worker-assets.js
The Current Working Directory (CWD) has to be at the wwwroot folder of the published application. You can now integrate these scripts into your build process or continuous integration pipeline, as done in this GitHub Actions workflow:
name: Deploy to GitHub Pages
# Run workflow on every push to the master branch
on:
push:
branches: [pwa]
jobs:
deploy-to-github-pages:
# use ubuntu-latest image to run steps on
runs-on: ubuntu-latest
steps:
# uses GitHub's checkout action to checkout code form the master branch
- uses: actions/checkout@v2
# sets up .NET Core SDK 5.0.101
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.101
# publishes Blazor project to the release-folder
- name: Publish .NET Core Project
run: dotnet publish BlazorGitHubPagesDemo.csproj -c Release -o release --nologo
# changes the base-tag in index.html from '/' to 'BlazorGitHubPagesDemo' to match GitHub Pages repository subdirectory
- name: Change base-tag in index.html from / to BlazorGitHubPagesDemo
run: sed -i 's/<base href="\/" \/>/<base href="\/BlazorGitHubPagesDemo\/" \/>/g' release/wwwroot/index.html
# changes the base-tag in index.html from '/' to 'BlazorGitHubPagesDemo' to match GitHub Pages repository subdirectory
- name: Fix service-worker-assets.js hashes
working-directory: release/wwwroot
run: |
jsFile=$(<service-worker-assets.js)
# remove JavaScript from contents so it can be interpreted as JSON
json=$(echo "$jsFile" | sed "s/self.assetsManifest = //g" | sed "s/;//g")
# grab the assets JSON array
assets=$(echo "$json" | jq '.assets[]' -c)
for asset in $assets
do
oldHash=$(echo "$asset" | jq '.hash')
#remove leading and trailing quotes
oldHash="${oldHash:1:-1}"
path=$(echo "$asset" | jq '.url')
#remove leading and trailing quotes
path="${path:1:-1}"
newHash="sha256-$(openssl dgst -sha256 -binary $path | openssl base64 -A)"
if [$oldHash != $newHash]; then
# escape slashes for json
oldHash=$(echo "$oldHash" | sed 's;/;\\/;g')
newHash=$(echo "$newHash" | sed 's;/;\\/;g')
echo "Updating hash for $path from $oldHash to $newHash"
# escape slashes second time for sed
oldHash=$(echo "$oldHash" | sed 's;/;\\/;g')
jsFile=$(echo -n "$jsFile" | sed "s;$oldHash;$newHash;g")
fi
done
echo -n "$jsFile" > service-worker-assets.js
# copy index.html to 404.html to serve the same file when a file is not found
- name: copy index.html to 404.html
run: cp release/wwwroot/index.html release/wwwroot/404.html
# add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project. (Allow files and folders starting with an underscore)
- name: Add .nojekyll file
run: touch release/wwwroot/.nojekyll
- name: Commit wwwroot to GitHub Pages
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: release/wwwroot
Hopefully,this helps and saves you some valuable time, cheers!
Top comments (0)