forked from jsnbuchanan/crowd-funder-for-time-pwa
Compare commits
20 Commits
more-small
...
web-push-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f0f1b7fc8 | ||
|
|
cfc4d0a947 | ||
|
|
8684488def | ||
|
|
a820a7b131 | ||
| 30d45c0acf | |||
| 221bb2a27c | |||
| 2961e29831 | |||
| 5ae5e110c2 | |||
| 20c2954be1 | |||
| a848e1fa81 | |||
|
|
ee28b18b14 | ||
|
|
2d38183dce | ||
|
|
082a6eae1f | ||
|
|
d07fb47721 | ||
|
|
ccb6160bca | ||
|
|
2eaa4203aa | ||
|
|
f27a18c712 | ||
|
|
2c4a920c3c | ||
|
|
ed91cadd9d | ||
|
|
a6de282aec |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@ node_modules
|
||||
signature.bin
|
||||
*.pem
|
||||
verified.txt
|
||||
myenv
|
||||
|
||||
*~
|
||||
# local env files
|
||||
|
||||
30
additional-scripts.js
Normal file
30
additional-scripts.js
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
|
||||
self.addEventListener("push", function (event) {
|
||||
let payload;
|
||||
if (event.data) {
|
||||
payload = JSON.parse(event.data.text());
|
||||
}
|
||||
|
||||
const title = payload ? payload.title : "Custom Title";
|
||||
const options = {
|
||||
body: payload ? payload.body : "Custom body text",
|
||||
icon: payload ? payload.icon : "icon.png",
|
||||
badge: payload ? payload.badge : "badge.png",
|
||||
};
|
||||
|
||||
event.waitUntil(self.registration.showNotification(title, options));
|
||||
});
|
||||
|
||||
|
||||
self.addEventListener('message', function(event) {
|
||||
const data = event.data;
|
||||
|
||||
switch (data.command) {
|
||||
case 'account':
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('Unknown command:', data.command);
|
||||
}
|
||||
});
|
||||
26470
package-lock.json
generated
26470
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
72
package.json
72
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kickstart-for-time-pwa",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
@@ -9,59 +9,58 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersproject/hdnode": "^5.7.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.3",
|
||||
"@pvermeer/dexie-encrypted-addon": "^3.0.0",
|
||||
"@tweenjs/tween.js": "^21.0.0",
|
||||
"@veramo/core": "^5.2.0",
|
||||
"@veramo/credential-w3c": "^5.2.0",
|
||||
"@veramo/data-store": "^5.2.0",
|
||||
"@veramo/did-manager": "^5.1.2",
|
||||
"@veramo/did-provider-ethr": "^5.1.2",
|
||||
"@veramo/did-resolver": "^5.2.0",
|
||||
"@veramo/key-manager": "^5.1.2",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"@veramo/core": "^5.4.1",
|
||||
"@veramo/credential-w3c": "^5.4.1",
|
||||
"@veramo/data-store": "^5.4.1",
|
||||
"@veramo/did-manager": "^5.4.1",
|
||||
"@veramo/did-provider-ethr": "^5.4.1",
|
||||
"@veramo/did-resolver": "^5.4.1",
|
||||
"@veramo/key-manager": "^5.4.1",
|
||||
"@vueuse/core": "^10.4.1",
|
||||
"@zxing/text-encoding": "^0.9.0",
|
||||
"axios": "^1.4.0",
|
||||
"axios": "^1.5.0",
|
||||
"buffer": "^6.0.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
"core-js": "^3.31.1",
|
||||
"core-js": "^3.32.1",
|
||||
"dexie": "^3.2.4",
|
||||
"dexie-export-import": "^4.0.7",
|
||||
"did-jwt": "^7.2.4",
|
||||
"ethereum-cryptography": "^2.0.0",
|
||||
"did-jwt": "^7.2.7",
|
||||
"ethereum-cryptography": "^2.1.2",
|
||||
"ethereumjs-util": "^7.1.5",
|
||||
"ethr-did-resolver": "^8.0.0",
|
||||
"ethr-did-resolver": "^8.1.2",
|
||||
"jdenticon": "^3.2.0",
|
||||
"js-generate-password": "^0.1.9",
|
||||
"localstorage-slim": "^2.4.0",
|
||||
"luxon": "^3.3.0",
|
||||
"localstorage-slim": "^2.5.0",
|
||||
"luxon": "^3.4.3",
|
||||
"merkletreejs": "^0.3.10",
|
||||
"moment": "^2.29.4",
|
||||
"notiwind": "^2.0.2",
|
||||
"papaparse": "^5.4.1",
|
||||
"pina": "^0.20.2204228",
|
||||
"pinia-plugin-persistedstate": "^3.1.0",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"qr-code-generator-vue3": "^1.4.21",
|
||||
"ramda": "^0.29.0",
|
||||
"readable-stream": "^4.4.2",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"three": "^0.154.0",
|
||||
"three": "^0.156.1",
|
||||
"vue": "^3.3.4",
|
||||
"vue-axios": "^3.5.2",
|
||||
"vue-facing-decorator": "^2.1.20",
|
||||
"vue-qrcode-reader": "^5.3.4",
|
||||
"vue-router": "^4.2.3",
|
||||
"vue-facing-decorator": "^3.0.2",
|
||||
"vue-router": "^4.2.4",
|
||||
"web-did-resolver": "^2.0.27"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/leaflet": "^1.9.4",
|
||||
"@types/ramda": "^0.29.3",
|
||||
"@types/three": "^0.152.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.61.0",
|
||||
"@typescript-eslint/parser": "^5.61.0",
|
||||
"@types/three": "^0.155.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.6.0",
|
||||
"@typescript-eslint/parser": "^6.6.0",
|
||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||
@@ -71,16 +70,15 @@
|
||||
"@vue/cli-plugin-vuex": "~5.0.8",
|
||||
"@vue/cli-service": "~5.0.8",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^5.0.0-alpha.1",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"postcss": "^8.4.24",
|
||||
"prettier": "^3.0.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "~5.1.6"
|
||||
},
|
||||
"pkgx": "node^18 npm^10"
|
||||
"postcss": "^8.4.29",
|
||||
"prettier": "^3.0.3",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "~5.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
tasks:
|
||||
|
||||
- in endorser-push-server - mount folder for persistent sqlite DB outside of container
|
||||
- test alerts on all pages -- or refactor to new "notify" (since AlertMessage refactoring may require a change, et. ContactQRScanShowView)
|
||||
- 40 notifications :
|
||||
- push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew
|
||||
|
||||
@@ -11,10 +10,9 @@ tasks:
|
||||
- 01 fix the Discovery map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui
|
||||
- .1 add instructions for map location selection
|
||||
|
||||
- 01 Show pop-up or some message confirming that settings & contacts download has been initiated/finished assignee:matthew assignee-group:ui
|
||||
- 01 Show pop-up or some message confirming that settings & contacts download has been initiated/finished
|
||||
|
||||
- 01 Ensure each action sent to the server has a confirmation - eg registration (ie a toast something that dismisses after 5-10s) assignee-group:ui
|
||||
- SEE: https://github.com/emmanuelsw/notiwind assignee:jose assignee-group:ui
|
||||
- 01 Ensure each action sent to the server has a confirmation - eg registration (ie a toast something that dismisses after 5-10s)
|
||||
|
||||
- Home Feed & Quick Give screen :
|
||||
- 01 save the feed-viewed status in settings storage ("afterQuery")
|
||||
@@ -23,18 +21,16 @@ tasks:
|
||||
|
||||
- 24 Move to Vite assignee:matthew
|
||||
|
||||
- .2 fit as many icons as possible on home & project view screens but only going halfway down the page
|
||||
- .1 Remove notification alert visuals on home page
|
||||
- .5 switch so DiscoverView shows anywhere by default, and no number unless search is done (and maybe a better filter UI, including "mine" to consolidate with ProjectsView)
|
||||
- .2 fit as many icons as possible on home & project view screens but only going halfway down the page assignee-group:ui
|
||||
- .5 Add infinite scroll to gifts on the home page
|
||||
- .5 bug - search for "Safari" does not find the project, but if already on the "Anywhere" tab it shows all
|
||||
- .2 figure out why endorser-mobile search doesn't find recently created PlanAction
|
||||
- .1 when creating a plan, select location and then make sure you can deselect on Android
|
||||
- .5 include a version, maybe the hash of the latest commit -- figuring out how it works on prod now
|
||||
- .5 add link to further project / people when a project pays ahead
|
||||
- .5 add project ID to the URL, to make a project publicly-accessible
|
||||
- .5 add project ID to the URL of the project-view, to make a project publicly-accessible
|
||||
- .5 fix where user 0 sees no txns from user 1 on contacts page but sees them on list page
|
||||
- .2 on ProjectViewView, show different messages for "to" and "from" sections if none exist assignee-group:ui
|
||||
- .2 fix static icon to the right on project page (Matthew - I've made "Rotary" into issuer?) assignee:jose assignee-group:ui
|
||||
- .2 on ProjectViewView, show different messages for "to" and "from" sections if none exist
|
||||
- .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent
|
||||
- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164
|
||||
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
|
||||
@@ -44,14 +40,12 @@ tasks:
|
||||
- Discuss whether the remaining tasks are worthwhile before MVP release.
|
||||
|
||||
- 04 allow user to download claims, mine + ones I can see about me from others
|
||||
- .5 change the derivation path, and regenerate test IDs
|
||||
- 02 allow user to create new DIDs from the same seed phrase (ie. increment derivation path)
|
||||
- .5 on ProjectView page, show immediate feedback when a gift is given (on list?) -- and consider the same for Home & Contacts pages assignee-group:ui
|
||||
- .5 on ProjectView page, show immediate feedback when a gift is given (on list?) -- and consider the same for Home & Contacts pages
|
||||
- .5 customize favicon assignee-group:ui
|
||||
- .5 Do we want to combine first name & last name?
|
||||
- .2 Show a warning if both giver and recipient are the same (but still allow?) assignee-group:ui
|
||||
- .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 assignee-group:ui
|
||||
- .5 Display a more appealing confirmation on the map when erasing the marker
|
||||
- .5 make a VC details page
|
||||
- .1 Add units or different icon to the coins (to distinguish $, BTC, hours, etc)
|
||||
- .1 remove firstName (& lastName) from localStorage
|
||||
@@ -63,6 +57,7 @@ tasks:
|
||||
|
||||
- stats v1 :
|
||||
- 01 show numeric stats
|
||||
- 04 show different graphic for projects vs people on world
|
||||
- 01 link to world for specific stats
|
||||
- .5 don't load another instance of a bush if it already exists
|
||||
- maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version")
|
||||
|
||||
175
src/App.vue
175
src/App.vue
@@ -162,17 +162,22 @@
|
||||
|
||||
<button
|
||||
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
||||
@click="
|
||||
close(notification.id);
|
||||
turnOnNotifications();
|
||||
"
|
||||
>
|
||||
Turn on Notifications
|
||||
</button>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
@click="close(notification.id)"
|
||||
@click="maybeLater(notification.id)"
|
||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||
>
|
||||
Maybe Later
|
||||
</button>
|
||||
<button
|
||||
@click="never(notification.id)"
|
||||
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md"
|
||||
>
|
||||
Never
|
||||
@@ -254,4 +259,170 @@
|
||||
|
||||
<style></style>
|
||||
|
||||
<script lang="ts"></script>
|
||||
<script lang="ts">
|
||||
import { Vue, Component } from "vue-facing-decorator";
|
||||
import axios from "axios";
|
||||
|
||||
@Component
|
||||
export default class App extends Vue {
|
||||
b64 = "";
|
||||
mounted() {
|
||||
axios
|
||||
.get("https://timesafari-pwa.anomalistlabs.com/web-push/vapid")
|
||||
.then((response) => {
|
||||
this.b64 = response.data.vapidKey;
|
||||
console.log(this.b64);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("API error", error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private askPermission(): Promise<NotificationPermission> {
|
||||
// Check if Notifications are supported
|
||||
if (!("Notification" in window)) {
|
||||
alert("This browser does not support notifications.");
|
||||
return Promise.reject("This browser does not support notifications.");
|
||||
}
|
||||
|
||||
// Check existing permissions
|
||||
if (Notification.permission === "granted") {
|
||||
return Promise.resolve("granted");
|
||||
}
|
||||
|
||||
// Request permission
|
||||
return new Promise((resolve, reject) => {
|
||||
const permissionResult = Notification.requestPermission((result) => {
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
if (permissionResult) {
|
||||
permissionResult.then(resolve, reject);
|
||||
}
|
||||
}).then((permissionResult) => {
|
||||
console.log("Permission result:", permissionResult);
|
||||
|
||||
if (permissionResult !== "granted") {
|
||||
alert("We need notification permission to provide certain features.");
|
||||
return Promise.reject("We weren't granted permission.");
|
||||
}
|
||||
|
||||
return permissionResult;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async turnOnNotifications() {
|
||||
return this.askPermission()
|
||||
.then((permission) => {
|
||||
console.log("Permission granted:", permission);
|
||||
|
||||
// Call the function and handle promises
|
||||
this.subscribeToPush()
|
||||
.then(() => {
|
||||
console.log("Subscribed successfully.");
|
||||
// Assuming the subscription object is available
|
||||
return navigator.serviceWorker.ready;
|
||||
})
|
||||
.then((registration) => {
|
||||
// Fetch the existing subscription object from the registration
|
||||
return registration.pushManager.getSubscription();
|
||||
})
|
||||
.then((subscription) => {
|
||||
if (subscription) {
|
||||
console.log(subscription);
|
||||
return this.sendSubscriptionToServer(subscription);
|
||||
} else {
|
||||
throw new Error("Subscription object is not available.");
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Subscription data sent to server.");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
"Subscription or server communication failed:",
|
||||
error,
|
||||
);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("An error occurred:", error);
|
||||
// Handle error appropriately here
|
||||
});
|
||||
}
|
||||
|
||||
// Function to convert URL base64 to Uint8Array
|
||||
private urlBase64ToUint8Array(base64String: string): Uint8Array {
|
||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||
const base64 = (base64String + padding)
|
||||
.replace(/-/g, "+")
|
||||
.replace(/_/g, "/");
|
||||
const rawData = window.atob(base64);
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i);
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
// The subscribeToPush method
|
||||
private subscribeToPush(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if ("serviceWorker" in navigator && "PushManager" in window) {
|
||||
const applicationServerKey = this.urlBase64ToUint8Array(this.b64);
|
||||
const options: PushSubscriptionOptions = {
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: applicationServerKey,
|
||||
};
|
||||
console.log(options);
|
||||
|
||||
navigator.serviceWorker.ready
|
||||
.then((registration) => {
|
||||
return registration.pushManager.subscribe(options);
|
||||
})
|
||||
.then((subscription) => {
|
||||
console.log("Push subscription successful:", subscription);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Push subscription failed:", error, options);
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
const errorMsg = "Push messaging is not supported";
|
||||
console.warn(errorMsg);
|
||||
reject(new Error(errorMsg));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private sendSubscriptionToServer(
|
||||
subscription: PushSubscription,
|
||||
): Promise<void> {
|
||||
console.log(subscription);
|
||||
return fetch("/web-push/subscribe", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(subscription),
|
||||
}).then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to send subscription to server");
|
||||
}
|
||||
console.log("Subscription sent to server successfully.");
|
||||
});
|
||||
}
|
||||
|
||||
never(ID: string) {
|
||||
alert(ID);
|
||||
}
|
||||
|
||||
maybeLater(ID: string) {
|
||||
alert(ID);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* See also ../libs/veramo/setup.ts
|
||||
*/
|
||||
export enum AppString {
|
||||
APP_NAME = "Time Safari",
|
||||
APP_NAME = "TimeSafari",
|
||||
|
||||
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
|
||||
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
|
||||
|
||||
@@ -9,59 +9,39 @@ import {
|
||||
} from "./tables/settings";
|
||||
import { AppString } from "@/constants/app";
|
||||
|
||||
// a separate DB because the seed is super-sensitive data
|
||||
type SensitiveTables = {
|
||||
accounts: Table<Account>;
|
||||
};
|
||||
|
||||
// Define types for tables that hold sensitive and non-sensitive data
|
||||
type SensitiveTables = { accounts: Table<Account> };
|
||||
type NonsensitiveTables = {
|
||||
contacts: Table<Contact>;
|
||||
settings: Table<Settings>;
|
||||
};
|
||||
|
||||
/**
|
||||
* In order to make the next line be acceptable, the program needs to have its linter suppress a rule:
|
||||
* https://typescript-eslint.io/rules/no-unnecessary-type-constraint/
|
||||
*
|
||||
* and change *any* to *unknown*
|
||||
*
|
||||
* https://9to5answer.com/how-to-bypass-warning-unexpected-any-specify-a-different-type-typescript-eslint-no-explicit-any
|
||||
*/
|
||||
// Using 'unknown' instead of 'any' for stricter typing and to avoid TypeScript warnings
|
||||
export type SensitiveDexie<T extends unknown = SensitiveTables> = BaseDexie & T;
|
||||
export const accountsDB = new BaseDexie("TimeSafariAccounts") as SensitiveDexie;
|
||||
const SensitiveSchemas = Object.assign({}, AccountsSchema);
|
||||
|
||||
export type NonsensitiveDexie<T extends unknown = NonsensitiveTables> =
|
||||
BaseDexie & T;
|
||||
|
||||
// Initialize Dexie databases for sensitive and non-sensitive data
|
||||
export const accountsDB = new BaseDexie("TimeSafariAccounts") as SensitiveDexie;
|
||||
const SensitiveSchemas = { ...AccountsSchema };
|
||||
|
||||
export const db = new BaseDexie("TimeSafari") as NonsensitiveDexie;
|
||||
const NonsensitiveSchemas = Object.assign({}, ContactsSchema, SettingsSchema);
|
||||
const NonsensitiveSchemas = { ...ContactsSchema, ...SettingsSchema };
|
||||
|
||||
/**
|
||||
* Needed to enable a special webpack setting to allow *await* below:
|
||||
* https://stackoverflow.com/questions/72474803/error-the-top-level-await-experiment-is-not-enabled-set-experiments-toplevelaw
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create password and place password in localStorage.
|
||||
*
|
||||
* It's good practice to keep the data encrypted at rest, so we'll do that even
|
||||
* if the secret is stored right next to the app.
|
||||
*/
|
||||
// Manage the encryption key. If not present in localStorage, create and store it.
|
||||
const secret =
|
||||
localStorage.getItem("secret") || Encryption.createRandomEncryptionKey();
|
||||
if (!localStorage.getItem("secret")) localStorage.setItem("secret", secret);
|
||||
|
||||
if (localStorage.getItem("secret") == null) {
|
||||
localStorage.setItem("secret", secret);
|
||||
}
|
||||
|
||||
// Apply encryption to the sensitive database using the secret key
|
||||
encrypted(accountsDB, { secretKey: secret });
|
||||
accountsDB.version(1).stores(SensitiveSchemas);
|
||||
|
||||
// Define the schema for our databases
|
||||
accountsDB.version(1).stores(SensitiveSchemas);
|
||||
db.version(1).stores(NonsensitiveSchemas);
|
||||
|
||||
// initialize, a la https://dexie.org/docs/Tutorial/Design#the-populate-event
|
||||
db.on("populate", function () {
|
||||
// ensure there's an initial entry for settings
|
||||
// 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,
|
||||
|
||||
@@ -1,29 +1,46 @@
|
||||
/**
|
||||
* BoundingBox type describes the geographical bounding box coordinates.
|
||||
*/
|
||||
export type BoundingBox = {
|
||||
eastLong: number;
|
||||
maxLat: number;
|
||||
minLat: number;
|
||||
westLong: number;
|
||||
eastLong: number; // Eastern longitude
|
||||
maxLat: number; // Maximum (Northernmost) latitude
|
||||
minLat: number; // Minimum (Southernmost) latitude
|
||||
westLong: number; // Western longitude
|
||||
};
|
||||
|
||||
// a singleton
|
||||
/**
|
||||
* Settings type encompasses user-specific configuration details.
|
||||
*/
|
||||
export type Settings = {
|
||||
id: number; // there's only one entry: MASTER_SETTINGS_KEY
|
||||
id: number; // Only one entry using MASTER_SETTINGS_KEY
|
||||
activeDid?: string; // Active Decentralized ID
|
||||
apiServer?: string; // API server URL
|
||||
firstName?: string; // User's first name
|
||||
lastName?: string; // User's last name
|
||||
lastViewedClaimId?: string; // Last viewed claim ID
|
||||
lastNotifiedClaimId?: string; // Last notified claim ID
|
||||
|
||||
activeDid?: string;
|
||||
apiServer?: string;
|
||||
firstName?: string;
|
||||
isRegistered?: boolean;
|
||||
lastName?: string; // deprecated, pre v 0.1.3
|
||||
lastViewedClaimId?: string;
|
||||
// Array of named search boxes defined by bounding boxes
|
||||
|
||||
searchBoxes?: Array<{
|
||||
name: string;
|
||||
bbox: BoundingBox;
|
||||
}>;
|
||||
showContactGivesInline?: boolean;
|
||||
|
||||
showContactGivesInline?: boolean; // Display contact inline or not
|
||||
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
|
||||
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
|
||||
reminderOn?: boolean; // Toggle to enable or disable reminders
|
||||
};
|
||||
|
||||
/**
|
||||
* Schema for the Settings table in the database.
|
||||
*/
|
||||
export const SettingsSchema = {
|
||||
settings: "id",
|
||||
};
|
||||
|
||||
/**
|
||||
* Constants.
|
||||
*/
|
||||
export const MASTER_SETTINGS_KEY = 1;
|
||||
|
||||
@@ -79,14 +79,6 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "contacts" */ "../views/ContactsView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/scan-contact",
|
||||
name: "scan-contact",
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "scan-contact" */ "../views/ContactScanView.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/discover",
|
||||
name: "discover",
|
||||
@@ -99,6 +91,14 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "help" */ "../views/HelpView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/identity-switcher",
|
||||
name: "identity-switcher",
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "identity-switcher" */ "../views/IdentitySwitcherView.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/import-account",
|
||||
name: "import-account",
|
||||
@@ -147,14 +147,6 @@ const routes: Array<RouteRecordRaw> = [
|
||||
/* webpackChunkName: "new-identifier" */ "../views/NewIdentifierView.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/identity-switcher",
|
||||
name: "identity-switcher",
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "identity-switcher" */ "../views/IdentitySwitcherView.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/project",
|
||||
name: "project",
|
||||
@@ -168,6 +160,14 @@ const routes: Array<RouteRecordRaw> = [
|
||||
import(/* webpackChunkName: "projects" */ "../views/ProjectsView.vue"),
|
||||
beforeEnter: enterOrStart,
|
||||
},
|
||||
{
|
||||
path: "/scan-contact",
|
||||
name: "scan-contact",
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: "scan-contact" */ "../views/ContactScanView.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/seed-backup",
|
||||
name: "seed-backup",
|
||||
|
||||
@@ -98,8 +98,6 @@ export default class ProjectsView extends Vue {
|
||||
projects: ProjectData[] = [];
|
||||
current: IIdentifier;
|
||||
isLoading = false;
|
||||
alertTitle = "";
|
||||
alertMessage = "";
|
||||
numAccounts = 0;
|
||||
|
||||
async beforeCreate() {
|
||||
|
||||
@@ -11,5 +11,8 @@ module.exports = defineConfig({
|
||||
iconPaths: {
|
||||
faviconSVG: "img/icons/safari-pinned-tab.svg",
|
||||
},
|
||||
workboxOptions: {
|
||||
importScripts: ["additional-scripts.js"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
15
web-push.md
15
web-push.md
@@ -1,3 +1,6 @@
|
||||
|
||||
# Overivew of Web Push
|
||||
|
||||
Web Push notifications is a web browser messaging protocol defined by the W3C.
|
||||
|
||||
Discussions of this interesting technology are clouded because of a
|
||||
@@ -360,29 +363,29 @@ unsubscribeFromPush().catch((err) => {
|
||||
NOTE: We could offer an option within the app to "mute" these notifications. This wouldn't turn off the notifications at the browser level, but you could make it so that your Service Worker doesn't display them even if it receives them.
|
||||
|
||||
|
||||
## NOTIFICATION DIALOG WORKFLOW
|
||||
# NOTIFICATION DIALOG WORKFLOW
|
||||
|
||||
# ON APP FIRST-LAUNCH:
|
||||
## ON APP FIRST-LAUNCH:
|
||||
The user is periodically presented with the notification permission dialog that asks them if they want to turn on notifications. User is given 3 choices:
|
||||
|
||||
- "Turn on Notifications": triggers the browser's own notification permission prompt.
|
||||
- "Maybe Later": dismisses the dialog, to reappear at a later instance. (The next time the user launches the app? After X amount of days? A combination of both?)
|
||||
- "Never": dismisses the dialog; app remembers to not automatically present the dialog again.
|
||||
|
||||
# IF THE USER CHOOSES "NEVER":
|
||||
## IF THE USER CHOOSES "NEVER":
|
||||
The dialog can still be accessed via the Notifications toggle switch in `AccountViewView` (which also tells the user if notifications are turned on or off).
|
||||
|
||||
# TO TEMPORARILY MUTE NOTIFICATIONS:
|
||||
## TO TEMPORARILY MUTE NOTIFICATIONS:
|
||||
While notifications are turned on, the user can tap on the Mute Notifications toggle switch in `AccountViewView` (visible only when notifications are turned on) to trigger the Mute Notifications Dialog. User is given the following choices:
|
||||
|
||||
- Several "Mute for X Hour/s" buttons to temporarily mute notifications.
|
||||
- "Mute until I turn it back on" button to indefinitely mute notifications.
|
||||
- "Cancel" to make no changes and dismiss the dialog.
|
||||
|
||||
# TO UNMUTE NOTIFICATIONS:
|
||||
## TO UNMUTE NOTIFICATIONS:
|
||||
Simply tap on the Mute Notifications toggle switch in `AccountViewView` to immediately unmute notifications. No dialog needed.
|
||||
|
||||
# TO TURN OFF NOTIFICATIONS:
|
||||
## TO TURN OFF NOTIFICATIONS:
|
||||
While notifications are turned on, the user can tap on the App Notifications toggle switch in `AccountViewView` to trigger the Turn Off Notifications Dialog. User is given the following choices:
|
||||
|
||||
- "Turn off Notifications" to fully turn them off (which means the user will need to go through the dialogs agains to turn them back on).
|
||||
|
||||
Reference in New Issue
Block a user