Compare commits
26 Commits
claim-view
...
0.1.8
| Author | SHA1 | Date | |
|---|---|---|---|
| d26d1d3601 | |||
| 1e6159869f | |||
| 75d15ddeb9 | |||
| 051a0a97d8 | |||
| f8d3fe2ee1 | |||
| 4f0a046723 | |||
| c4a0458c08 | |||
| 25b1598fcb | |||
| ddbb700c34 | |||
| fd8877900b | |||
| 05c6ddda02 | |||
| 853eb3c623 | |||
| 44cfe0d88e | |||
| 7fe256dc9e | |||
| e739d0be7c | |||
| 8d873b51bd | |||
| d7f4acb702 | |||
| f8002c4550 | |||
| d6b1386741 | |||
| 50fdd95c60 | |||
| 91c6c7c11c | |||
| 4e28dc8de6 | |||
| fb425f0d51 | |||
| a19aebcb37 | |||
| d0697c1ef4 | |||
| 1dd2333624 |
20
CHANGELOG.md
@@ -9,7 +9,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [0.1.6] - 2023.12.17
|
||||
## [0.1.8] - 2023.12.27
|
||||
### Added
|
||||
- DB logging for service-worker events
|
||||
- Help page for notifications
|
||||
- Test notification & web-push triggers inside app
|
||||
- Check that the app is installed
|
||||
### Fixed
|
||||
- Project issuer display name
|
||||
|
||||
|
||||
## [0.1.7] - 2023.12.19 - 91c6c7c11c71f96006cc876fc946f1f98a274ba2
|
||||
### Changed
|
||||
- Icons
|
||||
### Fixed
|
||||
- Notification switch now shows message
|
||||
- Prod/test server warning message at top of page
|
||||
|
||||
|
||||
## [0.1.6] - 2023.12.17 - b445b1234fbfcf6b37d695373f259aab0eda1118
|
||||
### Added
|
||||
- Infinite scroll on home page
|
||||
### Changed
|
||||
|
||||
25
README.md
@@ -22,19 +22,17 @@ 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`. Increment version, add "-beta", `npm install`, and commit.
|
||||
* [Tag wth the new version.](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases)
|
||||
|
||||
* 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`
|
||||
|
||||
* Revert src/constants/app.ts
|
||||
* `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/`
|
||||
|
||||
@@ -42,6 +40,8 @@ If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js,
|
||||
|
||||
* `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntu@endorser.ch:time-safari`
|
||||
|
||||
* Revert src/constants/app.ts and/or package.json, edit package.json to increment version & add "-beta", `npm install`, and commit.
|
||||
|
||||
|
||||
|
||||
## Tests
|
||||
@@ -112,10 +112,12 @@ 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.)
|
||||
|
||||
(If you find more, add them to the HelpNotificationsView.vue file.)
|
||||
|
||||
|
||||
|
||||
@@ -138,3 +140,4 @@ Gifts make the world go 'round!
|
||||
* [Many tools & libraries]() such as Nodejs.org, IntelliJ Idea, Veramo.io, Vuejs.org, threejs.org
|
||||
* [Bush 3D model](https://sketchfab.com/3d-models/lupine-plant-bf30f1110c174d4baedda0ed63778439)
|
||||
* [Forest floor image](https://www.goodfreephotos.com/albums/textures/leafy-autumn-forest-floor.jpg)
|
||||
* Time Safari logo assisted by [DALL-E in ChatGPT](https://chat.openai.com/g/g-2fkFE8rbu-dall-e)
|
||||
|
||||
8
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "TimeSafari",
|
||||
"version": "0.1.7-beta",
|
||||
"name": "TimeSafari_Test",
|
||||
"version": "0.1.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "TimeSafari",
|
||||
"version": "0.1.7-beta",
|
||||
"name": "TimeSafari_Test",
|
||||
"version": "0.1.8",
|
||||
"dependencies": {
|
||||
"@ethersproject/hdnode": "^5.7.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "TimeSafari",
|
||||
"version": "0.1.7-beta",
|
||||
"name": "TimeSafari_Test",
|
||||
"version": "0.1.8",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
|
||||
@@ -2,31 +2,19 @@
|
||||
tasks:
|
||||
|
||||
- 08 notifications :
|
||||
- get it to work on Android - background data in apps
|
||||
- get it to work on iOS
|
||||
- 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
|
||||
- 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
|
||||
- .2 after turning on notification, don't wait in push server but wait in client for message test
|
||||
- insert tooling (exportable logs?) so that we can see problems and troubleshoot as we onboard
|
||||
- if navigator.serviceWorker is null, then tell the user to wait
|
||||
- Local install works after cleared out cache in Chrome
|
||||
|
||||
- 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")
|
||||
- fix maskable icon
|
||||
|
||||
- give users next public key hash
|
||||
|
||||
- .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 debugging
|
||||
- add way to test quickly 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)
|
||||
@@ -46,9 +34,16 @@ 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)
|
||||
- 02 watch for the service worker activation before showing the button to turn on notifications
|
||||
- 01 server - show all claim details when issued by the issuer
|
||||
- bug - got error adding on Firefox user #0 as contact for themselves
|
||||
- bug (that is hard to reproduce) - 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
|
||||
- bug (that is hard to reproduce) - in Chrome, install app then delete app and try from Chrome browser and see log errors "Uncaught TypeError: self.appendDailyLog is not a function"
|
||||
- bug (that is hard to reproduce) - on the second 'give' recorded on prod it showed me as the agent
|
||||
- 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
|
||||
@@ -57,12 +52,13 @@ tasks:
|
||||
- .2 Show a warning if both giver and recipient are the same (but still allow?)
|
||||
- 01 Would it look better to shrink the buttons on many pages so they don't expand to the width of the screen? assignee-group:ui
|
||||
- .5 Display a more appealing confirmation on the map when erasing the marker
|
||||
- .5 include the hash of the latest commit on help page next to version (maybe Trent's git-hash branch)
|
||||
- .5 remove references to localStorage for projectId (now that it's pulling from the path)
|
||||
- bug (that is hard to reproduce) - on the second 'give' recorded on prod it showed me as the agent
|
||||
- switch some checks for activeDid to check for isRegistered
|
||||
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
|
||||
- .5 fix cert generation on server (since it didn't happen automatically for Nov 30)
|
||||
- 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
|
||||
|
||||
- 04 fix lack of initial notification in Firefox (on MacOS, maybe others)
|
||||
|
||||
@@ -85,6 +81,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
|
||||
|
||||
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 463 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 799 B After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 215 B After Width: | Height: | Size: 37 KiB |
37
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;
|
||||
}
|
||||
@@ -329,10 +329,7 @@ export default class App extends Vue {
|
||||
}
|
||||
} catch (error) {
|
||||
if (window.location.host.startsWith("localhost")) {
|
||||
console.log(
|
||||
"Ignoring this error getting VAPID for local development:",
|
||||
error,
|
||||
);
|
||||
console.log("Ignoring the error getting VAPID for local development.");
|
||||
} else {
|
||||
console.error("Got an error initializing notifications:", error);
|
||||
this.$notify(
|
||||
@@ -373,6 +370,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.");
|
||||
}
|
||||
@@ -423,7 +421,7 @@ export default class App extends Vue {
|
||||
});
|
||||
}
|
||||
|
||||
async turnOnNotifications() {
|
||||
public async turnOnNotifications() {
|
||||
return this.askPermission()
|
||||
.then((permission) => {
|
||||
console.log("Permission granted:", permission);
|
||||
@@ -445,7 +443,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 +471,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 +518,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(
|
||||
@@ -531,7 +534,7 @@ export default class App extends Vue {
|
||||
private sendSubscriptionToServer(
|
||||
subscription: PushSubscription,
|
||||
): Promise<void> {
|
||||
console.log("About to send subscription", subscription);
|
||||
console.log("About to send subscription...", subscription);
|
||||
return fetch("/web-push/subscribe", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
||||
BIN
src/assets/help/apple-icon.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
src/assets/help/chrome-install-pwa.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/assets/help/mac-installed-app-settings.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
src/assets/help/windows-system-enable-notifications.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -30,15 +30,15 @@ export default class TopMessage extends Vue {
|
||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||
if (
|
||||
settings?.warnIfTestServer &&
|
||||
window.location.hostname !== AppString.PROD_TIME_SAFARI_SERVER_HOST
|
||||
settings.apiServer !== AppString.PROD_ENDORSER_API_SERVER
|
||||
) {
|
||||
const didPrefix = settings.activeDid?.slice(11, 14);
|
||||
this.message = "You're linked to a test server, user " + didPrefix;
|
||||
const didPrefix = settings.activeDid?.slice(11, 15);
|
||||
this.message = "You're linked to a non-prod server, user " + didPrefix;
|
||||
} else if (
|
||||
settings?.warnIfProdServer &&
|
||||
window.location.hostname === AppString.PROD_TIME_SAFARI_SERVER_HOST
|
||||
settings.apiServer === AppString.PROD_ENDORSER_API_SERVER
|
||||
) {
|
||||
const didPrefix = settings.activeDid?.slice(1, 14);
|
||||
const didPrefix = settings.activeDid?.slice(11, 15);
|
||||
this.message =
|
||||
"You're linked to the production server, user " + didPrefix;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ export default class TopMessage extends Vue {
|
||||
title: "Error Detecting Server",
|
||||
text: JSON.stringify(err),
|
||||
},
|
||||
10000,
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,25 +4,20 @@
|
||||
* See also ../libs/veramo/setup.ts
|
||||
*/
|
||||
export enum AppString {
|
||||
APP_NAME = "Time Safari",
|
||||
|
||||
PROD_TIME_SAFARI_SERVER_HOST = "timesafari.app",
|
||||
PROD_TIME_SAFARI_SERVER = "https://" + PROD_TIME_SAFARI_SERVER_HOST,
|
||||
|
||||
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
|
||||
|
||||
@@ -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 "./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,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
Normal file
@@ -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",
|
||||
};
|
||||
@@ -486,6 +486,10 @@ export interface ProjectData {
|
||||
* URL referencing information about the project
|
||||
**/
|
||||
handleId: string;
|
||||
/**
|
||||
* The DID of the issuer
|
||||
*/
|
||||
issuerDid: string;
|
||||
/**
|
||||
* The Identier of the project
|
||||
**/
|
||||
|
||||
@@ -13,6 +13,7 @@ import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import {
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
faArrowUpRightFromSquare,
|
||||
faBan,
|
||||
faBitcoinSign,
|
||||
faBurst,
|
||||
@@ -65,6 +66,7 @@ import {
|
||||
library.add(
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
faArrowUpRightFromSquare,
|
||||
faBan,
|
||||
faBitcoinSign,
|
||||
faBurst,
|
||||
|
||||
@@ -105,27 +105,10 @@
|
||||
</router-link>
|
||||
|
||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
|
||||
<label
|
||||
v-if="notificationUnchanged"
|
||||
for="toggleNotifications"
|
||||
<div
|
||||
v-if="!notificationMaybeChanged"
|
||||
class="flex items-center justify-between cursor-pointer"
|
||||
@click="
|
||||
!toggleNotifications
|
||||
? this.$notify(
|
||||
{
|
||||
group: 'modal',
|
||||
type: 'notification-permission',
|
||||
},
|
||||
-1,
|
||||
)
|
||||
: this.$notify(
|
||||
{
|
||||
group: 'modal',
|
||||
type: 'notification-off',
|
||||
},
|
||||
-1,
|
||||
)
|
||||
"
|
||||
@click="showNotificationChoice()"
|
||||
>
|
||||
<!-- label -->
|
||||
<div>App Notifications</div>
|
||||
@@ -134,8 +117,8 @@
|
||||
<!-- input -->
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="toggleNotifications"
|
||||
name="toggleNotifications"
|
||||
v-model="isSubscribed"
|
||||
name="toggleNotificationsInput"
|
||||
class="sr-only"
|
||||
/>
|
||||
<!-- line -->
|
||||
@@ -145,11 +128,14 @@
|
||||
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
||||
></div>
|
||||
</div>
|
||||
</label>
|
||||
<label v-else>
|
||||
</div>
|
||||
<div v-else>
|
||||
Notification status may have changed. Revisit this page to see the
|
||||
latest setting.
|
||||
</label>
|
||||
</div>
|
||||
<router-link class="px-4 text-sm text-blue-500" to="/help-notifications">
|
||||
Test your notification setup.
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<h3 class="text-sm uppercase font-semibold mb-3">Data</h3>
|
||||
@@ -311,7 +297,7 @@
|
||||
:to="{ name: 'statistics' }"
|
||||
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||
>
|
||||
See Global Animated History of Giving
|
||||
See Animated Global History of Giving
|
||||
</router-link>
|
||||
|
||||
<!-- id used by puppeteer test script -->
|
||||
@@ -323,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
|
||||
@@ -390,7 +369,7 @@
|
||||
@click="toggleTestWarning"
|
||||
>
|
||||
<!-- label -->
|
||||
<h2>Show warning if on test server</h2>
|
||||
<h2>Show warning if on non-prod server</h2>
|
||||
<!-- toggle -->
|
||||
<div class="relative ml-2">
|
||||
<!-- input -->
|
||||
@@ -492,7 +471,8 @@ export default class AccountViewView extends Vue {
|
||||
derivationPath = "";
|
||||
givenName = "";
|
||||
isRegistered = false;
|
||||
notificationUnchanged = true;
|
||||
isSubscribed = false;
|
||||
notificationMaybeChanged = false;
|
||||
numAccounts = 0;
|
||||
publicHex = "";
|
||||
publicBase64 = "";
|
||||
@@ -514,15 +494,6 @@ export default class AccountViewView extends Vue {
|
||||
warnIfProdServer = false;
|
||||
warnIfTestServer = false;
|
||||
|
||||
private isSubscribed = false;
|
||||
get toggleNotifications() {
|
||||
return this.isSubscribed;
|
||||
}
|
||||
set toggleNotifications(value) {
|
||||
this.isSubscribed = value;
|
||||
this.notificationUnchanged = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async function executed when the component is created.
|
||||
* Initializes the component's state with values from the database,
|
||||
@@ -553,10 +524,9 @@ export default class AccountViewView extends Vue {
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
this.subscription = await registration.pushManager.getSubscription();
|
||||
this.toggleNotifications = !!this.subscription;
|
||||
this.isSubscribed = !!this.subscription;
|
||||
} catch (error) {
|
||||
console.error("Mount error:", error);
|
||||
this.toggleNotifications = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,6 +657,31 @@ export default class AccountViewView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
async showNotificationChoice() {
|
||||
if (!this.subscription) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "notification-permission",
|
||||
title: "", // unused, only here to satisfy type check
|
||||
text: "", // unused, only here to satisfy type check
|
||||
},
|
||||
-1,
|
||||
);
|
||||
} else {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "notification-off",
|
||||
title: "", // unused, only here to satisfy type check
|
||||
text: "", // unused, only here to satisfy type check
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
this.notificationMaybeChanged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors and updates the component's state accordingly.
|
||||
* @param {Error} err - The error object.
|
||||
@@ -725,12 +720,12 @@ export default class AccountViewView extends Vue {
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error Updating Contact Setting",
|
||||
text: "Clear your cache and start over (after data backup).",
|
||||
text: "The setting may not have saved. Try again, maybe after restarting the app.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
console.error(
|
||||
"Telling user to clear cache after contact setting update because:",
|
||||
"Telling user to try again after contact setting update because:",
|
||||
err,
|
||||
);
|
||||
}
|
||||
@@ -748,12 +743,12 @@ export default class AccountViewView extends Vue {
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error Updating Prod Warning",
|
||||
text: "Clear your cache and start over (after data backup).",
|
||||
text: "The setting may not have saved. Try again, maybe after restarting the app.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
console.error(
|
||||
"Telling user to clear cache after contact setting update because:",
|
||||
"Telling user to try again after setting update because:",
|
||||
err,
|
||||
);
|
||||
}
|
||||
@@ -771,12 +766,12 @@ export default class AccountViewView extends Vue {
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error Updating Test Warning",
|
||||
text: "Clear your cache and start over (after data backup).",
|
||||
text: "The setting may not have saved. Try again, maybe after restarting the app.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
console.error(
|
||||
"Telling user to clear cache after contact setting update because:",
|
||||
"Telling user to try again after setting update because:",
|
||||
err,
|
||||
);
|
||||
}
|
||||
@@ -1049,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>
|
||||
|
||||
@@ -25,6 +25,13 @@
|
||||
<span class="justify-around">(Only 50 most recent)</span>
|
||||
<span />
|
||||
</div>
|
||||
<div class="flex justify-around">
|
||||
<span />
|
||||
<span class="justify-around">
|
||||
(This does not include claims by them if they're not visible to you.)
|
||||
</span>
|
||||
<span />
|
||||
</div>
|
||||
|
||||
<!-- Results List -->
|
||||
<table
|
||||
@@ -178,6 +185,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 +193,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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -908,13 +908,13 @@ export default class ContactsView extends Vue {
|
||||
},
|
||||
-1,
|
||||
);
|
||||
} else if (!parseFloat(this.hourInput)) {
|
||||
} else if (parseFloat(this.hourInput) == 0 && !this.hourDescriptionInput) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Input Error",
|
||||
text: "Giving 0 hours does nothing.",
|
||||
text: "Giving no hours or descrption does nothing.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
|
||||
@@ -277,8 +277,8 @@ export default class DiscoverView extends Vue {
|
||||
const plans: ProjectData[] = results.data;
|
||||
if (plans) {
|
||||
for (const plan of plans) {
|
||||
const { name, description, handleId, rowid } = plan;
|
||||
this.projects.push({ name, description, handleId, rowid });
|
||||
const { name, description, handleId, issuerDid, rowid } = plan;
|
||||
this.projects.push({ name, description, handleId, issuerDid, rowid });
|
||||
}
|
||||
this.remoteCount = this.projects.length;
|
||||
} else {
|
||||
@@ -360,8 +360,14 @@ export default class DiscoverView extends Vue {
|
||||
if (beforeId) {
|
||||
const plans: ProjectData[] = results.data;
|
||||
for (const plan of plans) {
|
||||
const { name, description, handleId = plan.handleId, rowid } = plan;
|
||||
this.projects.push({ name, description, handleId, rowid });
|
||||
const { name, description, handleId, issuerDid, rowid } = plan;
|
||||
this.projects.push({
|
||||
name,
|
||||
description,
|
||||
handleId,
|
||||
issuerDid,
|
||||
rowid,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.projects = results.data;
|
||||
|
||||
@@ -22,15 +22,104 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>Here are things to try to get notifications working.</p>
|
||||
<p>Here are ways to test notifications and get them working.</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">Test</h2>
|
||||
<p>Somehow call the service-worker self.showNotification</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">Check OS-level permissions</h2>
|
||||
<h2 class="text-xl font-semibold">Full Test</h2>
|
||||
<div>
|
||||
Walk-throughs & screenshots, maybe for all combinations of OS &
|
||||
browsers.
|
||||
<p>
|
||||
If this works then you're all set.
|
||||
<button
|
||||
@click="sendTestWebPushMessage(true)"
|
||||
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||
>
|
||||
Send Yourself a Test Web Push Message (Through Push Server but
|
||||
Skipping Client Filter)
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold">If this app is not installed...</h2>
|
||||
<div>
|
||||
<p>
|
||||
For best results on mobile, install this app on your device (as
|
||||
opposed to using it inside the broser app). In Chrome, it may prompt
|
||||
you, and you can also look for the "Install" command in the browser
|
||||
settings; on the the deskop, look for this icon in the address bar:
|
||||
<img
|
||||
src="../assets/help/chrome-install-pwa.png"
|
||||
alt="Chrome 'install' icon"
|
||||
class="ml-4"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold">
|
||||
If "you must enable notifications"...
|
||||
</h2>
|
||||
<div>
|
||||
<p>
|
||||
Wait for about 10 seconds (for the service worker to activate), then
|
||||
<button class="text-blue-500" @click="showNotificationChoice()">
|
||||
click here.
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold">Check App Permissions</h2>
|
||||
<div>
|
||||
<p>
|
||||
In Apple iOS, check "Settings" -> "Notifications", look for the Time
|
||||
Safari app (or the browser you're using), and make sure notifications
|
||||
are enabled.
|
||||
</p>
|
||||
<p>
|
||||
In Android, hold on to the app icon, then select "App Info", then
|
||||
"Notifications" and make sure they're enabled. If it's still a problem
|
||||
then go further:
|
||||
</p>
|
||||
<p>
|
||||
If you installed the app with Chrome, make sure there are no other
|
||||
tabs with it open. Here are some ways to clear caches that can mess
|
||||
things up (and note that this clears out data from the installed app
|
||||
-- which is good to do while the app is installed):
|
||||
</p>
|
||||
<ul>
|
||||
<li class="list-disc ml-4">
|
||||
Go to Chrome "App Info", then "Storage & Cache" and "Clear Storage".
|
||||
</li>
|
||||
<li class="list-disc ml-4">
|
||||
Go to Chrome "Settings", then "Privacy and Security" and "Clear
|
||||
"Clear browsing data", then "Cookies and site data". Also make sure
|
||||
the "Time Range" at the top shows "All time".
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
On a Mac, go to "Settings" and check "Notifications".
|
||||
<img
|
||||
src="../assets/help/mac-installed-app-settings.png"
|
||||
alt="Mac app settings"
|
||||
class="ml-4"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold">Check Browser Permissions</h2>
|
||||
<div>
|
||||
<p>In Apple iOS, check Settings -> Notifications.</p>
|
||||
<p>In Android, check Settings -> Notifications.</p>
|
||||
|
||||
You can find more details about compatibility
|
||||
<a
|
||||
href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API#browser_compatibility"
|
||||
class="text-blue-500"
|
||||
target="_blank"
|
||||
>
|
||||
here <fa icon="arrow-up-right-from-square" class="fa-fw" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold">Check OS Permissions</h2>
|
||||
<div class="px-2">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold">Mobile Phone - Apple iOS</h3>
|
||||
<div>
|
||||
@@ -39,39 +128,113 @@
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-semibold">Mobile Phone - Google Android</h3>
|
||||
<div>See the browser section.</div>
|
||||
<div>
|
||||
We recommend Chrome. It must be version 42 or higher. Check your
|
||||
version under Settings -> About Chrome.
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-semibold">Desktop - Mac</h3>
|
||||
<div>Requires Mac OS 13.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
Requires Mac OS 13; see your macOS version under Apple -> About
|
||||
This Mac.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold">Check browser-level permissions</h2>
|
||||
<p>Walk-throughs & screenshots for browser settings</p>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold">Windows desktop</h3>
|
||||
In Windows, check Settings -> Notifications.
|
||||
<img
|
||||
src="../assets/help/windows-system-enable-notifications.png"
|
||||
alt="Windows system settings"
|
||||
class="ml-4"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold">Mobile Phone - Apple iOS</h3>
|
||||
<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>
|
||||
You can find more details about compatibility
|
||||
<a
|
||||
href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API#browser_compatibility"
|
||||
class="text-blue-500"
|
||||
target="_blank"
|
||||
>
|
||||
here <fa icon="arrow-up-right-from-square" class="fa-fw" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold">Explain full reset to start again</h2>
|
||||
<p>
|
||||
Walk-throughs for clearing everything & subscribing anew to get a
|
||||
message
|
||||
</p>
|
||||
<h2 class="text-xl font-semibold">Reinstall</h2>
|
||||
<div>
|
||||
<p>
|
||||
If all else fails, uninstall the app, ensure all the browser tabs with
|
||||
it are closed, and clear out caches and storage.
|
||||
</p>
|
||||
<ul class="ml-4 list-disc">
|
||||
<li>
|
||||
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.)
|
||||
</li>
|
||||
<li>
|
||||
Clear notification permission. (in Chrome, go to
|
||||
`chrome://settings/content/notifications`; in Firefox, go to
|
||||
`about:preferences` and search for "notifications".)
|
||||
</li>
|
||||
<li>
|
||||
Unregister service worker. (in Chrome, go to
|
||||
`chrome://serviceworker-internals/`; in Firefox, go to
|
||||
`about:serviceworkers`.)
|
||||
</li>
|
||||
<li>
|
||||
Clear Cache Storage. (in Chrome, in dev tools under Application; in
|
||||
Firefox, in dev tools under Storage.)
|
||||
</li>
|
||||
</ul>
|
||||
<p>Then reinstall the app.</p>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold">Auto-detection</h2>
|
||||
<p>Show results of auto-detection whether they're turned on</p>
|
||||
<button
|
||||
@click="showTestNotification()"
|
||||
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||
>
|
||||
Send Test Notification Directly to Device (Not Through Push Server)
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="alertWebPushSubscription()"
|
||||
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||
>
|
||||
Show Web Push Subscription Info
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="sendTestWebPushMessage(true)"
|
||||
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||
>
|
||||
Send Yourself a Test Web Push Message (Through Push Server but Skipping
|
||||
Client Filter)
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="sendTestWebPushMessage()"
|
||||
class="block w-full text-center text-md bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||
>
|
||||
Send Yourself a Test Web Push Message (Through Push Server and Client
|
||||
Filter)
|
||||
</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 +246,156 @@ 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(skipFilter: boolean = false) {
|
||||
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 a special value that tells the service worker to send a direct notification to the device, skipping filters.
|
||||
// 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: `Test, where you will see this message ${
|
||||
skipFilter ? "un" : ""
|
||||
}filtered.`,
|
||||
title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push",
|
||||
};
|
||||
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, depending on the filtering you chose.",
|
||||
},
|
||||
-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() {
|
||||
const TEST_NOTIFICATION_TITLE = "It Worked";
|
||||
navigator.serviceWorker.ready
|
||||
.then((registration) => {
|
||||
return registration.showNotification(TEST_NOTIFICATION_TITLE, {
|
||||
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 entitled '${TEST_NOTIFICATION_TITLE}'.`,
|
||||
},
|
||||
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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
showNotificationChoice() {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "notification-permission",
|
||||
title: "", // unused, only here to satisfy type check
|
||||
text: "", // unused, only here to satisfy type check
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -30,16 +30,17 @@
|
||||
<h2 class="text-xl font-semibold">What is the philosophy here?</h2>
|
||||
<p>
|
||||
We are building networks of people who want to grow a giving society.
|
||||
First of all, you can record ways you've seen people give, and that
|
||||
leaves a permanent record -- one that came from you, and the recipient
|
||||
can prove it was for them. This is personally gratifying, but it extends
|
||||
to broader work: volunteers can get confirmation of activity and
|
||||
selectively show off their contributions and network.
|
||||
First of all, you can see what people have given, and also recognize
|
||||
gifts you've seen, in a way that leaves a permanent record -- one that
|
||||
came from you, and the recipient can prove it was for them. This is
|
||||
personally gratifying, but it extends to broader work: volunteers get
|
||||
confirmation of activity, and selectively show off their contributions
|
||||
and network.
|
||||
</p>
|
||||
<p>
|
||||
You can also record projects and plans and invite others to collaborate.
|
||||
Soon you'll be able to see when others are interested and see how much
|
||||
they're willing to contribute, even if there are conditions.
|
||||
You can show giving and also offer help to ideas, based on others'
|
||||
willingness to help out, too. You can record your own ideas and invite
|
||||
others to collaborate.
|
||||
</p>
|
||||
<p>
|
||||
This app uses the power of cryptography to build a reputation, recording
|
||||
@@ -181,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>
|
||||
|
||||
@@ -10,6 +10,21 @@
|
||||
|
||||
<!-- show the actions for recognizing a give -->
|
||||
<div class="mb-8">
|
||||
<div
|
||||
v-if="!isInstalled()"
|
||||
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
|
||||
>
|
||||
<p>
|
||||
You should install this as an app.
|
||||
<router-link
|
||||
:to="{ name: 'help-notifications' }"
|
||||
class="text-blue-500"
|
||||
>
|
||||
Go here for instructions.
|
||||
</router-link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!activeDid"
|
||||
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
|
||||
@@ -234,6 +249,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,13 +257,25 @@ 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// from https://benborgers.com/posts/pwa-detect-installed
|
||||
isInstalled() {
|
||||
// For iOS
|
||||
if ("standalone" in window.navigator) return true;
|
||||
|
||||
// For Android
|
||||
if (window.matchMedia("(display-mode: standalone)").matches) return true;
|
||||
|
||||
// If neither is true, it's not installed
|
||||
return false;
|
||||
}
|
||||
|
||||
public async buildHeaders() {
|
||||
const headers: HeadersInit = {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
target="_blank"
|
||||
class="underline"
|
||||
>Map View
|
||||
<fa icon="arrow-up-right-from-square" class="fa-fw" />
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="url">
|
||||
|
||||
@@ -125,8 +125,8 @@ export default class ProjectsView extends Vue {
|
||||
if (resp.status === 200 || !resp.data.data) {
|
||||
const plans: ProjectData[] = resp.data.data;
|
||||
for (const plan of plans) {
|
||||
const { name, description, handleId, rowid } = plan;
|
||||
this.projects.push({ name, description, handleId, rowid });
|
||||
const { name, description, handleId, issuerDid, rowid } = plan;
|
||||
this.projects.push({ name, description, handleId, issuerDid, rowid });
|
||||
}
|
||||
} else {
|
||||
console.log("Bad server response & data:", resp.status, resp.data);
|
||||
|
||||
@@ -4,62 +4,121 @@ importScripts(
|
||||
"https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js",
|
||||
);
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
console.log("Adding event listener for:", event);
|
||||
importScripts(
|
||||
function logConsoleAndDb(message, arg1, arg2) {
|
||||
// in chrome://serviceworker-internals note that the arg1 and arg2 here will show as "[object Object]" in that page but will show as expandable objects in the console
|
||||
console.log(`${new Date().toISOString()} ${message}`, arg1, arg2);
|
||||
if (self.appendDailyLog) {
|
||||
let fullMessage = `${new Date().toISOString()} ${message}`;
|
||||
if (arg1) {
|
||||
fullMessage += `\n${JSON.stringify(arg1)}`;
|
||||
}
|
||||
if (arg2) {
|
||||
fullMessage += `\n${JSON.stringify(arg2)}`;
|
||||
}
|
||||
self.appendDailyLog(fullMessage);
|
||||
} else {
|
||||
// sometimes we get the error: "Uncaught TypeError: self.appendDailyLog is not a function"
|
||||
console.log("Not logging to DB because self.appendDailyLog doesn't exist.");
|
||||
}
|
||||
}
|
||||
|
||||
self.addEventListener("install", async (event) => {
|
||||
console.log("Service worker got install event. Importing scripts...", event);
|
||||
await importScripts(
|
||||
"safari-notifications.js",
|
||||
"nacl.js",
|
||||
"noble-curves.js",
|
||||
"noble-hashes.js",
|
||||
);
|
||||
// this should now be available
|
||||
logConsoleAndDb("Service worker imported all scripts.");
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
logConsoleAndDb("Service worker is 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());
|
||||
logConsoleAndDb("Service worker is activated.");
|
||||
});
|
||||
|
||||
self.addEventListener("push", function (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 a special value that tells the service worker to trigger its daily check.
|
||||
// See https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/c1ed026662e754348a5f91542680bd4f57e5b81e/app.py#L217
|
||||
const DAILY_UPDATE_TITLE = "DAILY_CHECK";
|
||||
|
||||
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
|
||||
// This is shared with the notification-test code and should be a constant. Look for the same name in HelpNotificationsView.vue
|
||||
// Make sure it is something other than the DAILY_UPDATE_TITLE.
|
||||
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
|
||||
|
||||
let title;
|
||||
let message = "Got some empty message.";
|
||||
if (payload && payload.title == DIRECT_PUSH_TITLE) {
|
||||
// skip any search logic and show the message directly
|
||||
title = "Direct Notification";
|
||||
message = payload.message || "No details were provided.";
|
||||
} else {
|
||||
// any other title will run through regular filtering logic
|
||||
if (payload && payload.title === DAILY_UPDATE_TITLE) {
|
||||
title = "Daily Update";
|
||||
} else {
|
||||
title = payload.title || "Update";
|
||||
}
|
||||
message = await self.getNotificationCount();
|
||||
}
|
||||
const message = await self.getNotificationCount();
|
||||
if (message) {
|
||||
console.log("Will notify user:", message);
|
||||
const title = payload ? payload.title : "Custom Title";
|
||||
const options = {
|
||||
body: message,
|
||||
icon: payload ? payload.icon : "icon.png",
|
||||
badge: payload ? payload.badge : "badge.png",
|
||||
};
|
||||
await self.registration.showNotification(title, options);
|
||||
logConsoleAndDb("Notified user:", options);
|
||||
} else {
|
||||
console.log("No notification message, so will not tell the user.");
|
||||
logConsoleAndDb("No notification message.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing the push event:", error);
|
||||
logConsoleAndDb("Error with push event", event, error);
|
||||
}
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("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 });
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(clients.claim());
|
||||
console.log("Service worker activated", event);
|
||||
logConsoleAndDb("Service worker posted a message.");
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
console.log("Got fetch event", event.request);
|
||||
logConsoleAndDb("Service worker got fetch event.", event);
|
||||
});
|
||||
|
||||
self.addEventListener("error", (event) => {
|
||||
console.error("Error in Service Worker:", event.message);
|
||||
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);
|
||||
console.error("Column:", event.colno);
|
||||
|
||||
@@ -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,78 +494,77 @@ 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;
|
||||
if ("lastNotifiedClaimId" in settings) {
|
||||
lastNotifiedClaimId = settings["lastNotifiedClaimId"];
|
||||
}
|
||||
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"];
|
||||
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(
|
||||
settings["apiServer"] + "/api/v2/report/claims",
|
||||
{
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
},
|
||||
);
|
||||
if (response.status == 200) {
|
||||
let json = await response.json();
|
||||
let claims = json["data"];
|
||||
let newClaims = 0;
|
||||
for (var i = 0; i < claims.length; i++) {
|
||||
let claim = claims[i];
|
||||
if (claim["id"] === lastNotifiedClaimId) {
|
||||
break;
|
||||
}
|
||||
newClaims++;
|
||||
}
|
||||
if (newClaims > 0) {
|
||||
result = `There are ${newClaims} new activities on TimeSafari`;
|
||||
}
|
||||
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:",
|
||||
response.status,
|
||||
response,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// 1 is our master settings ID; see MASTER_SETTINGS_KEY
|
||||
const settings = await getSettingById(1);
|
||||
let lastNotifiedClaimId = null;
|
||||
if ("lastNotifiedClaimId" in settings) {
|
||||
lastNotifiedClaimId = settings["lastNotifiedClaimId"];
|
||||
}
|
||||
const activeDid = settings["activeDid"];
|
||||
accounts = await fetchAllAccounts();
|
||||
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));
|
||||
|
||||
headers["Authorization"] = "Bearer " + (await accessToken(identifier));
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
settings["apiServer"] + "/api/v2/report/claims",
|
||||
{
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
},
|
||||
);
|
||||
if (response.status == 200) {
|
||||
const json = await response.json();
|
||||
const claims = json["data"];
|
||||
let newClaims = 0;
|
||||
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 Time Safari`;
|
||||
}
|
||||
const most_recent_notified = claims[0]["id"];
|
||||
await setMostRecentNotified(most_recent_notified);
|
||||
} else {
|
||||
console.error(
|
||||
"safari-notifications getNotificationsCount got a bad response status when fetching claims",
|
||||
response.status,
|
||||
response,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
self.appendDailyLog = appendDailyLog;
|
||||
self.getNotificationCount = getNotificationCount;
|
||||
self.decodeBase64 = decodeBase64;
|
||||
|
||||