The App
I'm building a PWA as a frontend for OpenAI's Whisper, using this API. The goal is to be able to share WhatsApp voice messages directly to the PWA to transcribe them. I am using Angular 16.2.2 with the @angular/pwa package.
Here is my codebase in its current state
The Problem
I cannot get the share_target to work. When I press share in WhatsApp, my PWA shows up correctly but when I select it, it opens and displays a HTTP ERROR 504 page.
According the answer to this question, all I need to do is replace the ngsw-worker.js with my own custom service worker which listens for the POST request and imports the ngsw-worker at the bottom. However, this seems to break the ngsw-worker somehow, giving me a 504 error.
The Relevant Code
custom-service-worker.js
(copied almost exactly from the answer in the post linked above)
self.addEventListener('fetch', event => {
// Handle the share_target shares
if (event.request.method === 'POST') {
// Make sure we're only getting shares to the share-target route
const path = event.request.url.split("/").pop();
if(path === "share-target"){
//Get the audio from the share request
event.request.formData().then(formData => {
// Find the correct client in order to share the results.
const clientId = event.resultingClientId !== "" ? event.resultingClientId : event.clientId;
self.clients.get(clientId).then(client => {
// Get the audio
const audio = formData.getAll('audio');
// Send it to the client
client.postMessage(
{
message: "newMedia",
audio: audio
}
);
});
});
}
}
});
importScripts('./ngsw-worker.js');
manifest.webmanifest (excerpt)
"share_target": {
"action": "/app/share-target",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "name",
"text": "description",
"url": "link",
"files": [
{
"name": "audio",
"accept": ["audio/*", ".mp3", ".wav", ".ogg"]
}
]
}
}
angular.json (excerpt)
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest",
"src/custom-service-worker.js"
]
app.module.ts (excerpt)
@NgModule({
declarations: [
(...)
],
imports: [
(...),
ServiceWorkerModule.register('custom-service-worker.js', {
enabled: !isDevMode(),
// Register the ServiceWorker as soon as the application is stable
// or after 30 seconds (whichever comes first).
registrationStrategy: 'registerWhenStable:30000'
}),
(...)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
share-target.component.ts (excerpt)
ngOnInit(): void {
navigator.serviceWorker.addEventListener('message', (event: MessageEvent) => {
this.test_outputs.push("Got message \n" + JSON.stringify(event.data));
if (event.data.hasOwnProperty('audio')) {
this.api.transcribe(event.data).subscribe(res => {
this.test_outputs.push("Got response \n" + JSON.stringify(res));
});
}
});
}
What I tried
I tried importing the ngsw-worker at the top instead of the bottom of my custom service worker (as done here and here for example), but that resulted in the POST request created by the share_target API to be sent directly to the server, which resulted in my app showing nginx's 405 METHOD NOT ALLOWED error. This made me think that the problem here could somehow be connected to the Angular router, as it shouldn't try to load any html from the server in this situation per my understanding.
I had the same problem when trying to share images to the application. I was able to solve the problem comparing my function with this MDN POST example. It seems the lack of a response to the fetch request intercepted in the eventListener caused the service worker to return 504 (Gateway Timeout).
The solution would be to send a Response to the fetch event received. Example from the link (comments removed):
In the example event.respondWith returns a response, which will avoid the timeout. The Response is a redirect response which will redirect to "responseUrl", as the MDN page says that
I believe any response would avoid the error, but a redirect response should be the best option, considering the information above and that you will probably want ro redirect the user to the component where the data is going to be used.