Browse Source

Merge pull request 'Daily service-worker logging' (#100) from sw-log into master

Reviewed-on: https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/pulls/100
kb/add-usage-guide
trentlarson 11 months ago
parent
commit
ddbb700c34
  1. 20
      README.md
  2. 2
      package.json
  3. 39
      project.task.yaml
  4. 28
      src/App.vue
  5. 4
      src/components/GiftedDialog.vue
  6. 4
      src/components/OfferDialog.vue
  7. 2
      src/components/TopMessage.vue
  8. 12
      src/constants/app.ts
  9. 18
      src/db/index.ts
  10. 2
      src/db/tables/contacts.ts
  11. 10
      src/db/tables/logs.ts
  12. 18
      src/views/AccountViewView.vue
  13. 3
      src/views/ContactAmountsView.vue
  14. 3
      src/views/ContactGiftingView.vue
  15. 170
      src/views/HelpNotificationsView.vue
  16. 9
      src/views/HelpView.vue
  17. 3
      src/views/HomeView.vue
  18. 66
      sw_scripts/additional-scripts.js
  19. 102
      sw_scripts/safari-notifications.js

20
README.md

@ -22,25 +22,25 @@ npm run lint
If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
* `npx prettier --write ./sw_scripts/`
...to make sure the service worker scripts are in proper form
* Update the CHANGELOG.md & the version in package.json, run `npm install`, and commit.
* Tag wth the new version: `git tag 0.1.0`.
* If production, change src/constants/app.ts DEFAULT_*_SERVER to be PROD.
* If production, change src/constants/app.ts DEFAULT_*_SERVER to be "PROD" and package.json to remove "_Test".
* `npm run build`
* `npx prettier --write ./sw_scripts/`
...to make sure the service worker scripts are in proper form. It's only important if you changed something in that directory.
* `cp sw_scripts/[ns]* dist/`
... to copy the contents of the `sw_scripts` folder to the `dist` folder - except additional_scripts.js.
* `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntu@endorser.ch:time-safari`
* Revert src/constants/app.ts, increment version, add "-beta", `npm install`, and commit.
* Revert src/constants/app.ts and/or package.json, edit package.json to increment version & add "-beta", `npm install`, and commit.
@ -112,10 +112,10 @@ To add an icon, add to main.ts and reference with `fa` element and `icon` attrib
### Clear/Reset data & restart
* Clear cache for site. (In Chrome, go to `chrome://settings/cookies` and "all site data and permissions"; in Firefox, go to `about:preferences` and search for "cache" then "Manage Data".)
* Unregister service worker (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers`).
* Clear notification permission (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search for "notifications").
* Clear Cache Storage (in Chrome, in dev tools under Application; in Firefox, in dev tools under Storage).
* Clear cache for site. (In Chrome, go to `chrome://settings/cookies` and "all site data and permissions"; in Firefox, go to `about:preferences` and search for "cache" then "Manage Data", and also manually remove the IndexedDB data if the DBs still show.)
* Clear notification permission. (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search for "notifications".)
* Unregister service worker. (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers`.)
* Clear Cache Storage. (in Chrome, in dev tools under Application; in Firefox, in dev tools under Storage.)

2
package.json

@ -1,5 +1,5 @@
{
"name": "TimeSafari",
"name": "TimeSafari_Test",
"version": "0.1.8-beta",
"private": true,
"scripts": {

39
project.task.yaml

@ -2,29 +2,30 @@
tasks:
- 08 notifications :
- make the app behave correctly when App Notifications are turned off
- write troubleshooting docs for notifications
- hide the "App Notifications" toggle when they switch notifications
- prompt user to install on their home screen https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getInstalledRelatedApps
- get the rest of our Android devices to work
- insert tooling (exportable logs?) so that we can see problems and troubleshoot as we onboard
- get an error registering notifications on Firefox and subscription info is null
- if navigator.serviceWorker is null, then tell the user to wait
- Android DuckDuckGo asked for my permissions, got error, won't download DB
- Android Chrome won't ask permission, will download log but always empty
- Firefox works
- Local install works after cleared out cache in Chrome
- create troubleshooting notification:
- server gets signal to send a normal notification back immediately
- add windows & mac help at OS & browser level, in HelpNotificationsView.vue (linked from account page)
maybe tell them to pause, after first turn-on and after test
maybe Google Play permissions
- prompt user to install on their home screen https://benborgers.com/posts/pwa-detect-installed
- warn if they're using the web (android only?)
https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getInstalledRelatedApps
https://web.dev/articles/get-installed-related-apps
- add windows & mac help at OS & browser level
- back-and-forth on discovery & project pages led to "You need an identity to load your projects." error on product page when I had an identity
- fix the projects on /discover to show the issuer (currently all "Someone Anonymous")
- .2 fix the projects on /discover to show the issuer (currently all "Someone Anonymous")
- .3 bug - make or edit a project, choose "Include location", and see the map display shows on top of the bottom icons assignee-group:ui
- Got error adding on Firefox user #0 as contact for themselves
- .5 If notifications are not enabled, add message to front page with link/button to enable
- 01 server - show all claim details when issued by the issuer
- enhance help page instructions for friends to get notifications, for debugging notifications
- add way to test a push notification
- help instructions for PWA install problems (secret failed, must reinstall)
- look at other examples for better UI friend.tech
- show VC details... somehow:
- 01 show my VCs - most interesting, or via search
- 01 allow download of each VC (& confirmations, to show that they actually own their data)
@ -44,9 +45,14 @@ tasks:
- Other features - donation vs give, show offers, show give & outstanding totals, show network view, restrict registration, connect to contacts
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
- 01 send visibility signal as a VC and store it
- remove 'rowid' references (that are sqlite-specific)
- make identicons for contacts into more-memorable faces (and maybe change project identicons, too)
- 04 split out notification logic & tests from web-push-subscription logic & tests
- 01 server - show all claim details when issued by the issuer
- bug - got error adding on Firefox user #0 as contact for themselves
- bug - back-and-forth on discovery & project pages led to "You need an identity to load your projects." error on product page when I had an identity
- 01 send visibility signal as a VC and store it
- 04 remove 'rowid' references (that are sqlite-specific); may involve server
- 04 look at other examples for better UI friend.tech
- 01 make the prod build copy the sw_scripts
- .5 Add start date to project
- .3 check that Android shows "back" buttons on screens without bottom tray
@ -83,6 +89,7 @@ tasks:
- 24 Move to Vite
- 32 accept images for projects
- 32 accept images for contacts
- import project interactions from GitHub/GitLab and manage signing
- linking between projects or plans :
- show total time given to & from a project

28
src/App.vue

@ -281,7 +281,7 @@ interface VapidResponse {
};
}
import { AppString } from "@/constants/app";
import { DEFAULT_PUSH_SERVER } from "@/constants/app";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@ -302,7 +302,7 @@ export default class App extends Vue {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
let pushUrl: string = AppString.DEFAULT_PUSH_SERVER;
let pushUrl = DEFAULT_PUSH_SERVER;
if (settings?.webPushServer) {
pushUrl = settings.webPushServer;
}
@ -373,6 +373,7 @@ export default class App extends Vue {
}
private askPermission(): Promise<NotificationPermission> {
console.log("Requesting permission for notifications:", navigator);
if (!("serviceWorker" in navigator && navigator.serviceWorker.controller)) {
return Promise.reject("Service worker not available.");
}
@ -445,7 +446,18 @@ export default class App extends Vue {
}
})
.then(() => {
console.log("Subscription data sent to server.");
console.log(
"Subscription data sent to server and all finished successfully.",
);
this.$notify(
{
group: "alert",
type: "success",
title: "Notifications Turned On",
text: "Notifications are on. You should see one on your device; if not, see the 'Troubleshoot' page.",
},
-1,
);
})
.catch((error) => {
console.error(
@ -462,9 +474,7 @@ export default class App extends Vue {
"An error occurred setting notification permissions:",
error,
);
alert(
"Some error occurred setting notification permissions. See logs.",
);
alert("Some error occurred setting notification permissions.");
});
}
@ -511,11 +521,7 @@ export default class App extends Vue {
resolve();
})
.catch((error) => {
console.error(
"Subscription or server communication failed:",
error,
options,
);
console.error("Push subscription failed:", error, options);
// Inform the user about the issue
alert(

4
src/components/GiftedDialog.vue

@ -123,9 +123,7 @@ export default class GiftedDialog extends Vue {
group: "alert",
type: "danger",
title: "Error",
text:
err.message ||
"There was an error retrieving the latest sweet, sweet action.",
text: err.message || "There was an error retrieving your settings.",
},
-1,
);

4
src/components/OfferDialog.vue

@ -105,9 +105,7 @@ export default class OfferDialog extends Vue {
group: "alert",
type: "danger",
title: "Error",
text:
err.message ||
"There was an error retrieving the latest sweet, sweet action.",
text: err.message || "There was an error retrieving your settings.",
},
-1,
);

2
src/components/TopMessage.vue

@ -50,7 +50,7 @@ export default class TopMessage extends Vue {
title: "Error Detecting Server",
text: JSON.stringify(err),
},
10000,
-1,
);
}
}

12
src/constants/app.ts

@ -4,22 +4,20 @@
* See also ../libs/veramo/setup.ts
*/
export enum AppString {
APP_NAME = "Time Safari",
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
LOCAL_ENDORSER_API_SERVER = "http://localhost:3000",
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
PROD_PUSH_SERVER = "https://timesafari.app",
TEST1_PUSH_SERVER = "https://test.timesafari.app",
TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com",
DEFAULT_PUSH_SERVER = TEST1_PUSH_SERVER,
}
export const DEFAULT_ENDORSER_API_SERVER = AppString.TEST_ENDORSER_API_SERVER;
export const DEFAULT_PUSH_SERVER =
window.location.protocol + "//" + window.location.host;
/**
* The possible values for "group" and "type" are in App.vue.
* From the notiwind package

18
src/db/index.ts

@ -1,18 +1,20 @@
import BaseDexie, { Table } from "dexie";
import { encrypted, Encryption } from "@pvermeer/dexie-encrypted-addon";
import { Account, AccountsSchema } from "./tables/accounts";
import { Contact, ContactsSchema } from "./tables/contacts";
import { Contact, ContactSchema } from "./tables/contacts";
import { Log, LogSchema } from "@/db/tables/logs";
import {
MASTER_SETTINGS_KEY,
Settings,
SettingsSchema,
} from "./tables/settings";
import { AppString } from "@/constants/app";
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
// Define types for tables that hold sensitive and non-sensitive data
type SensitiveTables = { accounts: Table<Account> };
type NonsensitiveTables = {
contacts: Table<Contact>;
logs: Table<Log>;
settings: Table<Settings>;
};
@ -26,7 +28,11 @@ export const accountsDB = new BaseDexie("TimeSafariAccounts") as SensitiveDexie;
const SensitiveSchemas = { ...AccountsSchema };
export const db = new BaseDexie("TimeSafari") as NonsensitiveDexie;
const NonsensitiveSchemas = { ...ContactsSchema, ...SettingsSchema };
const NonsensitiveSchemas = {
...ContactSchema,
...LogSchema,
...SettingsSchema,
};
// Manage the encryption key. If not present in localStorage, create and store it.
const secret =
@ -38,12 +44,14 @@ encrypted(accountsDB, { secretKey: secret });
// Define the schema for our databases
accountsDB.version(1).stores(SensitiveSchemas);
db.version(1).stores(NonsensitiveSchemas);
// v1 was contacts & settings
// v2 added logs
db.version(2).stores(NonsensitiveSchemas);
// Event handler to initialize the non-sensitive database with default settings
db.on("populate", () => {
db.settings.add({
id: MASTER_SETTINGS_KEY,
apiServer: AppString.DEFAULT_ENDORSER_API_SERVER,
apiServer: DEFAULT_ENDORSER_API_SERVER,
});
});

2
src/db/tables/contacts.ts

@ -6,6 +6,6 @@ export interface Contact {
registered?: boolean;
}
export const ContactsSchema = {
export const ContactSchema = {
contacts: "&did, name, publicKeyBase64, registered, seesMe",
};

10
src/db/tables/logs.ts

@ -0,0 +1,10 @@
export interface Log {
message: string;
}
export const LogSchema = {
// Currently keyed by "date" because A) today's log data is what we need so we append, and
// B) we don't want it to grow so we remove everything if this is the first entry today.
// See safari-notifications.js logMessage for the associated logic.
logs: "date, message",
};

18
src/views/AccountViewView.vue

@ -133,6 +133,9 @@
Notification status may have changed. Revisit this page to see the
latest setting.
</div>
<router-link class="px-4 text-sm text-blue-500" to="/help-notifications">
Troubleshoot notifications here.
</router-link>
</div>
<h3 class="text-sm uppercase font-semibold mb-3">Data</h3>
@ -306,13 +309,6 @@
Switch Identity
</router-link>
<button
@click="alertWebPushSubscription()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
Show Subscription from Web Push Server
</button>
<div class="flex py-4">
<h2 class="text-slate-500 text-sm font-bold mb-2">Claim Server</h2>
<input
@ -1048,13 +1044,5 @@ export default class AccountViewView extends Vue {
-1,
);
}
alertWebPushSubscription() {
console.log(
"Web push subscription:",
JSON.parse(JSON.stringify(this.subscription)), // gives more info than plain console logging
);
alert(JSON.stringify(this.subscription));
}
}
</script>

3
src/views/ContactAmountsView.vue

@ -178,6 +178,7 @@ export default class ContactAmountssView extends Vue {
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
console.log("Error retrieving settings or gives.", err);
this.$notify(
{
group: "alert",
@ -185,7 +186,7 @@ export default class ContactAmountssView extends Vue {
title: "Error",
text:
err.userMessage ||
"There was an error retrieving the latest sweet, sweet action.",
"There was an error retrieving your settings and/or contacts and/or gives.",
},
-1,
);

3
src/views/ContactGiftingView.vue

@ -145,6 +145,7 @@ export default class ContactGiftingView extends Vue {
this.allContacts = await db.contacts.toArray();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
console.log("Error retrieving settings & contacts:", err);
this.$notify(
{
group: "alert",
@ -152,7 +153,7 @@ export default class ContactGiftingView extends Vue {
title: "Error",
text:
err.message ||
"There was an error retrieving the latest sweet, sweet action.",
"There was an error retrieving your settings and/or contacts.",
},
-1,
);

170
src/views/HelpNotificationsView.vue

@ -22,10 +22,16 @@
</div>
<div>
<p>Here are things to try to get notifications working.</p>
<p>Here are ways to get notifications working.</p>
<h2 class="text-xl font-semibold">Test</h2>
<p>Somehow call the service-worker self.showNotification</p>
<button
@click="showTestNotification()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
See a Test Notification
</button>
<h2 class="text-xl font-semibold">Check OS-level permissions</h2>
<div>
@ -54,7 +60,10 @@
<div>Make sure your OS (above) supports it.</div>
<h3 class="text-lg font-semibold">Mobile Phone - Android</h3>
<div>Chrome requires version 50.</div>
<div>
Chrome requires version 50. Hold icon, select "App info", and enable
notifications.
</div>
</div>
</div>
@ -66,12 +75,34 @@
<h2 class="text-xl font-semibold">Auto-detection</h2>
<p>Show results of auto-detection whether they're turned on</p>
<button
@click="alertWebPushSubscription()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
Show Subscription from Web Push Server
</button>
<button
@click="sendTestWebPushMessage()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
Send Myself a Test Web Push Message
</button>
</div>
</section>
</template>
<script lang="ts">
import axios from "axios";
import { Component, Vue } from "vue-facing-decorator";
import QuickNav from "@/components/QuickNav.vue";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { DEFAULT_PUSH_SERVER } from "@/constants/app";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;
interface Notification {
group: string;
@ -83,5 +114,140 @@ interface Notification {
@Component({ components: { QuickNav } })
export default class HelpNotificationsView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
subscription: PushSubscription | null = null;
async mounted() {
try {
const registration = await navigator.serviceWorker.ready;
this.subscription = await registration.pushManager.getSubscription();
} catch (error) {
console.error("Mount error:", error);
}
}
alertWebPushSubscription() {
console.log(
"Web push subscription:",
JSON.parse(JSON.stringify(this.subscription)), // gives more info than plain console logging
);
alert(JSON.stringify(this.subscription));
}
async sendTestWebPushMessage() {
if (!this.subscription) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Not Subscribed",
text: "You must enable notifications before testing the web push.",
},
-1,
);
return;
}
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
let pushUrl: string = DEFAULT_PUSH_SERVER as string;
if (settings?.webPushServer) {
pushUrl = settings.webPushServer;
}
// This is shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
// Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
const auth = Buffer.from(this.subscription.getKey("auth"));
const authB64 = auth
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const p256dh = Buffer.from(this.subscription.getKey("p256dh"));
const p256dhB64 = p256dh
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const newPayload = {
endpoint: this.subscription.endpoint,
keys: {
auth: authB64,
p256dh: p256dhB64,
},
message: "This is a test message, triggered by you.",
title: DIRECT_PUSH_TITLE,
};
console.log("Sending a test web push message:", newPayload);
const payloadStr = JSON.stringify(newPayload);
const response = await axios.post(
pushUrl + "/web-push/send-test",
payloadStr,
{
headers: {
"Content-Type": "application/json",
},
},
);
console.log("Got response from web push server:", response);
this.$notify(
{
group: "alert",
type: "success",
title: "Test Web Push Sent",
text: "Check your device for the test web push message.",
},
-1,
);
} catch (error) {
console.error("Got an error sending test notification:", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Sending Test",
text: "Got an error sending the test web push notification.",
},
-1,
);
}
}
showTestNotification() {
navigator.serviceWorker.ready
.then((registration) => {
return registration.showNotification("It Worked", {
body: "This is your test notification.",
});
})
.then(() => {
this.$notify(
{
group: "alert",
type: "success",
title: "Sent",
text: "A notification was triggered, so one should show on your device soon.",
},
5000,
);
})
.catch((error) => {
console.error("Got a notification error:", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Failed",
text: "Got an error sending a notification.",
},
-1,
);
});
}
}
</script>

9
src/views/HelpView.vue

@ -182,6 +182,15 @@
different page.
</p>
<h2 class="text-xl font-semibold">
Where do I get help with notifications?
</h2>
<p>
<router-link class="text-blue-500" to="/help-notifications"
>Here.</router-link
>
</p>
<h2 class="text-xl font-semibold">
How do I access even more functionality?
</h2>

3
src/views/HomeView.vue

@ -234,6 +234,7 @@ export default class HomeView extends Vue {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
console.log("Error retrieving settings and/or feed.", err);
this.$notify(
{
group: "alert",
@ -241,7 +242,7 @@ export default class HomeView extends Vue {
title: "Error",
text:
err.userMessage ||
"There was an error retrieving the latest sweet, sweet action.",
"There was an error retrieving your settings and/or the latest activity.",
},
-1,
);

66
sw_scripts/additional-scripts.js

@ -5,67 +5,103 @@ importScripts(
);
self.addEventListener("install", (event) => {
console.log(new Date().toISOString(), "Installing service worker:", event);
console.log("Service worker got install. Importing scripts...", event);
importScripts(
"safari-notifications.js",
"nacl.js",
"noble-curves.js",
"noble-hashes.js",
);
console.log("Service worker imported scripts.");
});
function logConsoleAndDb(message, arg1, arg2) {
console.log(`${new Date().toISOString()} ${message}`, arg1, arg2);
let fullMessage = `${new Date().toISOString()} ${message}`;
if (arg1) {
fullMessage += `\n${JSON.stringify(arg1)}`;
}
if (arg2) {
fullMessage += `\n${JSON.stringify(arg2)}`;
}
self.appendDailyLog(fullMessage);
}
self.addEventListener("push", function (event) {
console.log(new Date().toISOString(), "Received push event:", event);
let text = null;
if (event.data) {
text = event.data.text();
}
logConsoleAndDb("Service worker received a push event.", text, event);
event.waitUntil(
(async () => {
try {
let payload;
if (event.data) {
payload = JSON.parse(event.data.text());
if (text) {
try {
payload = JSON.parse(text);
} catch (e) {
// don't use payload since it is not JSON
}
}
// This is shared with the notification-test code and should be a constant. Look for the same name in HelpNotificationsView.vue
// Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
let title = "Generic Notification";
let message = "Got some empty message.";
if (payload && payload.title == DIRECT_PUSH_TITLE) {
// skip any search logic and show the message directly
title = "Direct Message";
message = payload.message || "No details were provided.";
} else {
title =
payload && payload.title ? payload.title : "Unknown Notification";
message = await self.getNotificationCount();
}
const message = await self.getNotificationCount();
if (message) {
const title = payload && payload.title ? payload.title : "Message";
const options = {
body: message,
icon: payload ? payload.icon : "icon.png",
badge: payload ? payload.badge : "badge.png",
};
await self.registration.showNotification(title, options);
console.log(new Date().toISOString(), "Notified user:", options);
logConsoleAndDb("Notified user:", options);
} else {
console.log(new Date().toISOString(), "No notification message.");
logConsoleAndDb("No notification message.");
}
} catch (error) {
console.error(new Date().toISOString(), "Error with push event", event, " - ", error);
logConsoleAndDb("Error with push event", event, error);
}
})(),
);
});
self.addEventListener("message", (event) => {
console.log(new Date().toISOString(), "Service worker message:", event);
logConsoleAndDb("Service worker got a message...", event);
if (event.data && event.data.type === "SEND_LOCAL_DATA") {
self.secret = event.data.data;
event.ports[0].postMessage({ success: true });
}
console.log(new Date().toISOString(), "Service worker posted message.");
logConsoleAndDb("Service worker posted a message.");
});
self.addEventListener("activate", (event) => {
console.log(new Date().toISOString(), "Service worker activating...", event);
logConsoleAndDb("Service worker activating...", event);
// see https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim
// and https://web.dev/articles/service-worker-lifecycle#clientsclaim
event.waitUntil(clients.claim());
console.log(new Date().toISOString(), "Service worker activated.");
logConsoleAndDb("Service worker is activated.");
});
self.addEventListener("fetch", (event) => {
console.log(new Date().toISOString(), "Got fetch event:", event);
logConsoleAndDb("Service worker got fetch event.", event);
});
self.addEventListener("error", (event) => {
console.error(new Date().toISOString(), "Error in Service Worker:", event);
logConsoleAndDb("Service worker error", event);
console.error("Full Error:", event);
console.error("Message:", event.message);
console.error("File:", event.filename);
console.error("Line:", event.lineno);

102
sw_scripts/safari-notifications.js

@ -395,12 +395,42 @@ async function setMostRecentNotified(id) {
data["lastNotifiedClaimId"] = id;
await updateRecord(store, data);
} else {
console.error("IndexedDB settings record not found.");
console.error(
"safari-notifications setMostRecentNotified IndexedDB settings record not found",
);
}
transaction.oncomplete = () => db.close();
} catch (error) {
console.error("IndexedDB error:", error);
console.error(
"safari-notifications setMostRecentNotified IndexedDB error",
error,
);
}
}
async function appendDailyLog(message) {
try {
const db = await openIndexedDB("TimeSafari");
const transaction = db.transaction("logs", "readwrite");
const store = transaction.objectStore("logs");
// will only keep one day's worth of logs
const todayKey = new Date().toDateString();
const previous = await getRecord(store, todayKey);
if (!previous) {
await store.clear(); // clear out anything older than today
}
let fullMessage = (previous && previous.message) || "";
if (fullMessage) {
fullMessage += "\n";
}
fullMessage += message;
await updateRecord(store, { date: todayKey, message: fullMessage });
transaction.oncomplete = () => db.close();
return true;
} catch (error) {
console.error("safari-notifications logMessage IndexedDB error", error);
return false;
}
}
@ -420,6 +450,7 @@ function getRecord(store, key) {
});
}
// Note that this assumes there is only one record in the store.
function updateRecord(store, data) {
return new Promise((resolve, reject) => {
const request = store.put(data);
@ -430,20 +461,23 @@ function updateRecord(store, data) {
async function fetchAllAccounts() {
return new Promise((resolve, reject) => {
let openRequest = indexedDB.open("TimeSafariAccounts");
const openRequest = indexedDB.open("TimeSafariAccounts");
openRequest.onupgradeneeded = function (event) {
let db = event.target.result;
const db = event.target.result;
if (!db.objectStoreNames.contains("accounts")) {
db.createObjectStore("accounts", { keyPath: "id" });
}
if (!db.objectStoreNames.contains("worker_log")) {
db.createObjectStore("worker_log");
}
};
openRequest.onsuccess = function (event) {
let db = event.target.result;
let transaction = db.transaction("accounts", "readonly");
let objectStore = transaction.objectStore("accounts");
let getAllRequest = objectStore.getAll();
const db = event.target.result;
const transaction = db.transaction("accounts", "readonly");
const objectStore = transaction.objectStore("accounts");
const getAllRequest = objectStore.getAll();
getAllRequest.onsuccess = function () {
resolve(getAllRequest.result);
@ -460,12 +494,8 @@ async function fetchAllAccounts() {
}
async function getNotificationCount() {
let secret = null;
let accounts = [];
let result = null;
if ("secret" in self) {
secret = self.secret;
const secretUint8Array = self.decodeBase64(secret);
// 1 is our master settings ID; see MASTER_SETTINGS_KEY
const settings = await getSettingById(1);
let lastNotifiedClaimId = null;
@ -474,29 +504,34 @@ async function getNotificationCount() {
}
const activeDid = settings["activeDid"];
accounts = await fetchAllAccounts();
let did = null;
for (var i = 0; i < accounts.length; i++) {
let account = accounts[i];
let did = account["did"];
if (did == activeDid) {
let publicKeyHex = account["publicKeyHex"];
let identity = account["identity"];
let activeAccount = null;
for (let i = 0; i < accounts.length; i++) {
if (accounts[i]["did"] == activeDid) {
activeAccount = accounts[i];
break;
}
}
const headers = {
"Content-Type": "application/json",
};
const identity = activeAccount && activeAccount["identity"];
if (identity && "secret" in self) {
const secret = self.secret;
const secretUint8Array = self.decodeBase64(secret);
const messageWithNonceAsUint8Array = self.decodeBase64(identity);
const nonce = messageWithNonceAsUint8Array.slice(0, 24);
const message = messageWithNonceAsUint8Array.slice(24, identity.length);
const decoder = new TextDecoder("utf-8");
const decrypted = self.secretbox.open(message, nonce, secretUint8Array);
const msg = decoder.decode(decrypted);
const identifier = JSON.parse(JSON.parse(msg));
const headers = {
"Content-Type": "application/json",
};
headers["Authorization"] = "Bearer " + (await accessToken(identifier));
}
let response = await fetch(
const response = await fetch(
settings["apiServer"] + "/api/v2/report/claims",
{
method: "GET",
@ -504,32 +539,29 @@ async function getNotificationCount() {
},
);
if (response.status == 200) {
let json = await response.json();
let claims = json["data"];
const json = await response.json();
const claims = json["data"];
let newClaims = 0;
for (var i = 0; i < claims.length; i++) {
let claim = claims[i];
for (let i = 0; i < claims.length; i++) {
const claim = claims[i];
if (claim["id"] === lastNotifiedClaimId) {
break;
}
newClaims++;
}
if (newClaims > 0) {
result = `There are ${newClaims} new activities on TimeSafari`;
result = `There are ${newClaims} new activities on Time Safari`;
}
const most_recent_notified = claims[0]["id"];
await setMostRecentNotified(most_recent_notified);
} else {
console.error(
"The service worker got a bad response status when fetching claims:",
"safari-notifications getNotificationsCount got a bad response status when fetching claims",
response.status,
response,
);
}
break;
}
}
}
return result;
}

Loading…
Cancel
Save