forked from trent_larson/crowd-funder-for-time-pwa
finish separation of daily reminder message, bump version to 0.3.34
This commit is contained in:
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
|
||||
## [0.3.34] - 2024.11
|
||||
### Added
|
||||
- Daily reliable, hard-coded notification message
|
||||
- Setting to change the partner API server
|
||||
|
||||
|
||||
## [0.3.33] - 2024.11.07 - adb7b16ecf1343c39cba71a7d6bb0e7a973e1102
|
||||
### Fixed
|
||||
- Affirm Delivery button on offer claim page didn't work.
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "TimeSafari",
|
||||
"version": "0.3.34-beta",
|
||||
"version": "0.3.34",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "TimeSafari",
|
||||
"version": "0.3.34-beta",
|
||||
"version": "0.3.34",
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^6.1.2",
|
||||
"@capacitor/cli": "^6.1.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "TimeSafari",
|
||||
"version": "0.3.34-beta",
|
||||
"version": "0.3.34",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"serve": "vite preview",
|
||||
|
||||
86
public/img/icons/safari-pinned-tab-512x512.svg
Normal file
86
public/img/icons/safari-pinned-tab-512x512.svg
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M2480 4005 c-25 -7 -58 -20 -75 -29 -16 -9 -40 -16 -52 -16 -17 0
|
||||
-24 -7 -28 -27 -3 -16 -14 -45 -24 -65 -21 -41 -13 -55 18 -38 25 13 67 13 92
|
||||
-1 15 -8 35 -4 87 17 99 39 130 41 197 10 64 -29 77 -31 107 -15 20 11 20 11
|
||||
-3 35 -12 13 -30 24 -38 24 -24 1 -132 38 -148 51 -8 7 -11 20 -7 32 12 37
|
||||
-40 47 -126 22z"/>
|
||||
<path d="M1450 3775 c-7 -8 -18 -15 -24 -15 -7 0 -31 -14 -54 -32 -29 -22 -38
|
||||
-34 -29 -40 17 -11 77 -10 77 1 0 5 16 16 35 25 60 29 220 19 290 -18 17 -9
|
||||
33 -16 37 -16 4 0 31 -15 60 -34 108 -70 224 -215 282 -353 30 -71 53 -190 42
|
||||
-218 -10 -27 -23 -8 -52 75 -30 90 -88 188 -120 202 -13 6 -26 9 -29 6 -3 -2
|
||||
11 -51 30 -108 28 -83 35 -119 35 -179 0 -120 -22 -127 -54 -17 -11 37 -13 21
|
||||
-18 -154 -5 -180 -8 -200 -32 -264 -51 -132 -129 -245 -199 -288 -21 -12 -79
|
||||
-49 -129 -80 -161 -102 -294 -141 -473 -141 -228 0 -384 76 -535 259 -81 99
|
||||
-118 174 -154 312 -31 121 -35 273 -11 437 19 127 19 125 -4 125 -23 0 -51
|
||||
-34 -87 -104 -14 -28 -33 -64 -41 -81 -19 -34 -22 -253 -7 -445 9 -106 12
|
||||
-119 44 -170 19 -30 42 -67 50 -81 64 -113 85 -140 130 -169 28 -18 53 -44 61
|
||||
-62 8 -20 36 -45 83 -76 62 -39 80 -46 151 -54 44 -5 96 -13 115 -18 78 -20
|
||||
238 -31 282 -19 24 6 66 8 95 5 76 -9 169 24 319 114 32 19 80 56 106 82 27
|
||||
26 52 48 58 48 5 0 27 26 50 58 48 66 56 70 132 71 62 1 165 29 238 64 112 55
|
||||
177 121 239 245 37 76 39 113 10 267 -12 61 -23 131 -26 156 -5 46 -5 47 46
|
||||
87 92 73 182 70 263 -8 l51 -49 -6 -61 c-4 -34 -13 -85 -21 -113 -28 -103 -30
|
||||
-161 -4 -228 16 -44 32 -67 55 -83 18 -11 39 -37 47 -58 10 -23 37 -53 73 -81
|
||||
32 -25 69 -57 82 -71 14 -14 34 -26 47 -26 12 0 37 -7 56 -15 20 -8 66 -17
|
||||
104 -20 107 -10 110 -11 150 -71 50 -75 157 -177 197 -187 18 -5 53 -24 78
|
||||
-42 71 -51 176 -82 304 -89 61 -4 127 -12 147 -18 29 -9 45 -8 77 6 23 9 50
|
||||
16 60 16 31 0 163 46 216 76 28 15 75 46 105 69 30 23 69 49 85 58 17 8 46 31
|
||||
64 51 19 20 40 36 47 36 18 0 77 70 100 120 32 66 45 108 55 173 5 32 16 71
|
||||
24 87 43 84 43 376 0 549 -27 105 -43 127 -135 188 -30 21 -65 46 -77 57 -13
|
||||
11 -23 17 -23 14 0 -3 21 -46 47 -94 79 -151 85 -166 115 -263 25 -83 28 -110
|
||||
28 -226 0 -144 -17 -221 -75 -335 -39 -77 -208 -244 -304 -299 -451 -263 -975
|
||||
-67 -1138 426 -23 70 -26 95 -28 254 -1 108 -7 183 -14 196 -6 12 -11 31 -11
|
||||
43 0 32 31 122 52 149 10 13 18 28 18 34 0 5 25 40 56 78 60 73 172 170 219
|
||||
190 30 12 30 13 6 17 -15 2 -29 -2 -37 -12 -6 -9 -16 -16 -22 -16 -6 0 -23
|
||||
-11 -39 -24 -15 -12 -33 -25 -40 -27 -17 -6 -82 -60 -117 -97 -65 -70 -75 -82
|
||||
-107 -133 -23 -34 -35 -46 -37 -35 -3 16 20 87 44 134 6 12 9 34 6 48 -4 22
|
||||
-8 25 -31 19 -14 -3 -38 -15 -53 -26 -34 -24 -34 -21 -6 28 65 112 184 206
|
||||
291 227 15 3 39 9 55 12 l27 6 -24 9 c-90 35 -304 -66 -478 -225 -39 -36 -74
|
||||
-66 -77 -66 -22 0 18 82 72 148 19 23 32 46 28 49 -4 4 -26 13 -49 19 -73 21
|
||||
-161 54 -171 64 -6 6 -20 10 -32 10 -21 0 -21 -1 -8 -40 45 -130 8 -247 -93
|
||||
-299 -25 -13 -31 0 -14 29 15 22 1 33 -22 17 -56 -36 -117 -22 -117 28 0 13
|
||||
-16 47 -35 76 -22 34 -33 60 -29 73 4 16 -3 26 -26 39 -16 10 -30 21 -30 25 1
|
||||
18 54 64 87 76 l38 13 -33 5 c-30 4 -115 -18 -154 -42 -13 -7 -20 -5 -27 8 -9
|
||||
16 -12 16 -53 1 -160 -61 -258 -104 -258 -114 0 -7 10 -20 21 -31 103 -91 217
|
||||
-297 249 -449 28 -135 41 -237 35 -276 -14 -91 -48 -170 -97 -220 -44 -47 -68
|
||||
-60 -68 -40 0 6 4 12 8 15 5 3 24 35 42 72 l33 67 -6 141 c-4 103 -11 158 -26
|
||||
205 -12 35 -21 70 -21 77 0 7 -20 56 -45 108 -82 173 -227 322 -392 401 -67
|
||||
33 -90 39 -163 42 -108 5 -130 10 -130 28 0 20 -63 20 -80 0z"/>
|
||||
<path d="M3710 3765 c0 -20 8 -28 39 -41 22 -8 42 -22 45 -30 5 -14 42 -19 70
|
||||
-8 10 4 -7 21 -58 55 -41 27 -79 49 -85 49 -6 0 -11 -11 -11 -25z"/>
|
||||
<path d="M3173 3734 c-9 -25 10 -36 35 -18 12 8 22 19 22 25 0 16 -50 10 -57
|
||||
-7z"/>
|
||||
<path d="M1982 3728 c6 -16 36 -34 44 -26 3 4 4 14 1 23 -7 17 -51 21 -45 3z"/>
|
||||
<path d="M1540 3620 c0 -5 7 -10 16 -10 8 0 12 5 9 10 -3 6 -10 10 -16 10 -5
|
||||
0 -9 -4 -9 -10z"/>
|
||||
<path d="M4467 3624 c-4 -4 23 -27 60 -50 84 -56 99 -58 67 -9 -28 43 -107 79
|
||||
-127 59z"/>
|
||||
<path d="M655 3552 c-11 -2 -26 -9 -33 -14 -7 -6 -27 -18 -45 -27 -36 -18 -58
|
||||
-64 -39 -83 9 -9 25 1 70 43 53 48 78 78 70 84 -2 1 -12 -1 -23 -3z"/>
|
||||
<path d="M1015 3460 c-112 -24 -247 -98 -303 -165 -53 -65 -118 -214 -136
|
||||
-311 -20 -113 -20 -145 -1 -231 20 -88 49 -153 102 -230 79 -113 186 -182 331
|
||||
-214 108 -24 141 -24 247 1 130 30 202 72 316 181 102 100 153 227 152 384 0
|
||||
142 -58 293 -150 395 -60 67 -180 145 -261 171 -75 23 -232 34 -297 19z m340
|
||||
-214 c91 -43 174 -154 175 -234 0 -18 -9 -51 -21 -73 -19 -37 -19 -42 -5 -64
|
||||
35 -54 12 -121 -48 -142 -22 -7 -47 -19 -55 -27 -9 -8 -41 -27 -71 -42 -50
|
||||
-26 -64 -29 -155 -29 -111 0 -152 14 -206 68 -49 49 -63 85 -64 162 0 59 4 78
|
||||
28 118 31 52 96 105 141 114 23 5 33 17 56 68 46 103 121 130 225 81z"/>
|
||||
<path d="M3985 3464 c-44 -7 -154 -44 -200 -67 -55 -28 -138 -96 -162 -132
|
||||
-10 -16 -39 -75 -64 -130 l-44 -100 0 -160 0 -160 45 -90 c53 -108 152 -214
|
||||
245 -264 59 -31 215 -71 281 -71 53 0 206 40 255 67 98 53 203 161 247 253 53
|
||||
113 74 193 74 280 -1 304 -253 564 -557 575 -49 2 -103 1 -120 -1z m311 -220
|
||||
c129 -68 202 -209 160 -309 -15 -35 -15 -42 -1 -72 26 -55 -3 -118 -59 -129
|
||||
-19 -3 -43 -15 -53 -26 -26 -29 -99 -64 -165 -78 -45 -10 -69 -10 -120 -1 -74
|
||||
15 -113 37 -161 91 -110 120 -50 331 109 385 24 8 44 23 52 39 6 14 18 38 25
|
||||
53 33 72 127 93 213 47z"/>
|
||||
<path d="M487 3394 c-21 -12 -27 -21 -25 -40 2 -14 7 -26 12 -27 14 -3 48 48
|
||||
44 66 -3 14 -6 14 -31 1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
81
src/App.vue
81
src/App.vue
@@ -238,6 +238,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="notification.type === 'notification-mute'"
|
||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||
@@ -277,6 +278,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="notification.type === 'notification-off'"
|
||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||
@@ -286,7 +288,7 @@
|
||||
>
|
||||
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
||||
<p class="text-lg mb-4">
|
||||
Would you like to <b>turn off</b> notifications for this app?
|
||||
Would you like to <b>turn off</b> this notification?
|
||||
</p>
|
||||
|
||||
<button
|
||||
@@ -296,7 +298,7 @@
|
||||
"
|
||||
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
|
||||
>
|
||||
Turn Off Notifications
|
||||
Turn Off Notification
|
||||
</button>
|
||||
<button
|
||||
@click="close(notification.id)"
|
||||
@@ -318,9 +320,8 @@
|
||||
<script lang="ts">
|
||||
import { Vue, Component } from "vue-facing-decorator";
|
||||
|
||||
import { db, logConsoleAndDb } from "@/db/index";
|
||||
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "@/db/index";
|
||||
import { NotificationIface } from "./constants/app";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
|
||||
@Component
|
||||
export default class App extends Vue {
|
||||
@@ -329,18 +330,29 @@ export default class App extends Vue {
|
||||
stopAsking = false;
|
||||
|
||||
async turnOffNotifications(notification: NotificationIface) {
|
||||
let subscription;
|
||||
const pushProviderSuccess: boolean = await navigator.serviceWorker?.ready
|
||||
let subscription: object | null = null;
|
||||
|
||||
let allGoingOff = false;
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
const notifyingNewActivity = !!settings?.notifyingNewActivityTime;
|
||||
const notifyingReminder = !!settings?.notifyingReminderTime;
|
||||
if (!notifyingNewActivity || !notifyingReminder) {
|
||||
// the other notification is already off, so fully unsubscribe now
|
||||
allGoingOff = true;
|
||||
}
|
||||
|
||||
await navigator.serviceWorker?.ready
|
||||
.then((registration) => {
|
||||
return registration.pushManager.getSubscription();
|
||||
})
|
||||
.then((subscript: PushSubscription | null) => {
|
||||
subscription = subscript;
|
||||
.then(async (subscript: PushSubscription | null) => {
|
||||
if (subscript) {
|
||||
return subscript.unsubscribe();
|
||||
subscription = subscript.toJSON();
|
||||
if (allGoingOff) {
|
||||
await subscript.unsubscribe();
|
||||
}
|
||||
} else {
|
||||
logConsoleAndDb("Subscription object is not available.");
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -348,15 +360,20 @@ export default class App extends Vue {
|
||||
"Push provider server communication failed: " + JSON.stringify(error),
|
||||
true,
|
||||
);
|
||||
return false;
|
||||
});
|
||||
|
||||
const pushServerSuccess: boolean = await fetch("/web-push/unsubscribe", {
|
||||
const serverSubscription = {
|
||||
...subscription,
|
||||
};
|
||||
if (!allGoingOff) {
|
||||
serverSubscription["notifyType"] = notification.title;
|
||||
}
|
||||
const pushServerSuccess = await fetch("/web-push/unsubscribe", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(subscription),
|
||||
body: JSON.stringify(serverSubscription),
|
||||
})
|
||||
.then((response) => {
|
||||
return response.ok;
|
||||
@@ -370,34 +387,24 @@ export default class App extends Vue {
|
||||
});
|
||||
|
||||
let message;
|
||||
if (pushProviderSuccess === pushServerSuccess) {
|
||||
message = "Both local and server notifications ";
|
||||
if (pushProviderSuccess) {
|
||||
message += "are off.";
|
||||
} else {
|
||||
message += "failed to turn off.";
|
||||
}
|
||||
if (pushServerSuccess) {
|
||||
message = "Notification is off.";
|
||||
} else {
|
||||
message =
|
||||
"Local unsubscribe " +
|
||||
(pushProviderSuccess ? "succeeded" : "failed") +
|
||||
" but server unsubscribe " +
|
||||
(pushServerSuccess ? "succeeded" : "failed") +
|
||||
".";
|
||||
message = "Notification is still on. Try to turn it off again.";
|
||||
}
|
||||
this.$notify({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Finished",
|
||||
text: message,
|
||||
});
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Finished",
|
||||
text: message,
|
||||
},
|
||||
5000,
|
||||
);
|
||||
|
||||
await db.open();
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
notifyingNewActivity: false,
|
||||
});
|
||||
if (notification.callback) {
|
||||
notification.callback(pushProviderSuccess && pushServerSuccess);
|
||||
// it's OK if the local notifications are still on (especially if the other notification is on)
|
||||
notification.callback(pushServerSuccess);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
>
|
||||
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
||||
<p v-if="serviceWorkerReady && vapidKey" class="text-lg mb-4">
|
||||
Would you like to be notified of new activity once a day?
|
||||
<span v-if="pushType === DAILY_CHECK_TITLE">
|
||||
Would you like to be notified of new activity, up to once a day?
|
||||
</span>
|
||||
<span v-else>
|
||||
Would you like to get a reminder message once a day?
|
||||
</span>
|
||||
</p>
|
||||
<p v-else class="text-lg mb-4">
|
||||
Waiting for system initialization, which may take up to 5 seconds...
|
||||
@@ -24,28 +29,51 @@
|
||||
</p>
|
||||
|
||||
<div v-if="serviceWorkerReady && vapidKey">
|
||||
<span class="flex flex-row justify-center">
|
||||
<span class="mt-2">Yes, tell me at: </span>
|
||||
<input
|
||||
type="number"
|
||||
@change="checkHourInput"
|
||||
class="rounded-l border border-r-0 border-slate-400 ml-2 mt-2 px-2 py-2 text-center w-20"
|
||||
v-model="hourInput"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
@change="checkMinuteInput"
|
||||
class="border border-slate-400 mt-2 px-2 py-2 text-center w-20"
|
||||
v-model="minuteInput"
|
||||
/>
|
||||
<span
|
||||
class="rounded-r border border-slate-400 bg-slate-200 text-center text-blue-500 mt-2 px-2 py-2 w-20"
|
||||
@click="hourAm = !hourAm"
|
||||
<div v-if="pushType === DAILY_CHECK_TITLE">
|
||||
<span>Yes, send me a message when there is new data for me</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span>Yes, send me this message:</span>
|
||||
<!-- eslint-disable -->
|
||||
<textarea
|
||||
type="text"
|
||||
id="push-message"
|
||||
v-model="messageInput"
|
||||
class="rounded border border-slate-400 mt-2 px-2 py-2 w-full"
|
||||
maxlength="100"
|
||||
></textarea
|
||||
>
|
||||
<span v-if="hourAm"> AM <fa icon="chevron-down" /> </span>
|
||||
<span v-else> PM <fa icon="chevron-up" /> </span>
|
||||
<!-- eslint-enable -->
|
||||
<span class="w-full flex justify-between text-xs text-slate-500">
|
||||
<span></span>
|
||||
<span>(100 characters max)</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="flex flex-row justify-center">
|
||||
<span class="mt-2">... at: </span>
|
||||
<input
|
||||
type="number"
|
||||
@change="checkHourInput"
|
||||
class="rounded-l border border-r-0 border-slate-400 ml-2 mt-2 px-2 py-2 text-center w-20"
|
||||
v-model="hourInput"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
@change="checkMinuteInput"
|
||||
class="border border-slate-400 mt-2 px-2 py-2 text-center w-20"
|
||||
v-model="minuteInput"
|
||||
/>
|
||||
<span
|
||||
class="rounded-r border border-slate-400 bg-slate-200 text-center text-blue-500 mt-2 px-2 py-2 w-20"
|
||||
@click="hourAm = !hourAm"
|
||||
>
|
||||
<span v-if="hourAm"> AM <fa icon="chevron-down" /> </span>
|
||||
<span v-else> PM <fa icon="chevron-up" /> </span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white mt-2 px-2 py-2 rounded-md"
|
||||
@click="
|
||||
@@ -73,12 +101,7 @@
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
|
||||
import {
|
||||
db,
|
||||
logConsoleAndDb,
|
||||
retrieveSettingsForActiveAccount,
|
||||
} from "@/db/index";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "@/db/index";
|
||||
import { urlBase64ToUint8Array } from "@/libs/crypto/vc/util";
|
||||
import * as libsUtil from "@/libs/util";
|
||||
|
||||
@@ -89,6 +112,7 @@ interface ErrorResponse {
|
||||
|
||||
// PushSubscriptionJSON is defined in the Push API https://www.w3.org/TR/push-api/#dom-pushsubscriptionjson
|
||||
interface PushSubscriptionWithTime extends PushSubscriptionJSON {
|
||||
message?: string;
|
||||
notifyTime: { utcHour: number; minute: number };
|
||||
notifyType: string;
|
||||
}
|
||||
@@ -115,17 +139,27 @@ export default class PushNotificationPermission extends Vue {
|
||||
// eslint-disable-next-line
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => Promise<() => void>;
|
||||
|
||||
callback: (success: boolean, time: string) => void = () => {};
|
||||
DAILY_CHECK_TITLE = libsUtil.DAILY_CHECK_TITLE;
|
||||
DIRECT_PUSH_TITLE = libsUtil.DIRECT_PUSH_TITLE;
|
||||
|
||||
callback: (success: boolean, time: string, message?: string) => void =
|
||||
() => {};
|
||||
hourAm = true;
|
||||
hourInput = "8";
|
||||
isVisible = false;
|
||||
messageInput = "";
|
||||
minuteInput = "00";
|
||||
pushType = "";
|
||||
serviceWorkerReady = false;
|
||||
vapidKey = "";
|
||||
|
||||
async open(callback?: (success: boolean, time: string) => void) {
|
||||
this.isVisible = true;
|
||||
async open(
|
||||
pushType: string,
|
||||
callback?: (success: boolean, time: string, message?: string) => void,
|
||||
) {
|
||||
this.callback = callback || this.callback;
|
||||
this.isVisible = true;
|
||||
this.pushType = pushType;
|
||||
try {
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
let pushUrl = DEFAULT_PUSH_SERVER;
|
||||
@@ -194,6 +228,18 @@ export default class PushNotificationPermission extends Vue {
|
||||
navigator.serviceWorker?.ready.then(() => {
|
||||
this.serviceWorkerReady = true;
|
||||
});
|
||||
|
||||
if (this.pushType === this.DIRECT_PUSH_TITLE) {
|
||||
this.messageInput =
|
||||
"Just a friendly reminder: click and share some gratitude with the world.";
|
||||
// focus on the message input
|
||||
setTimeout(function () {
|
||||
document.getElementById("push-message")?.focus();
|
||||
}, 100);
|
||||
} else {
|
||||
// not critical but doesn't make sense in a daily check
|
||||
this.messageInput = "";
|
||||
}
|
||||
}
|
||||
|
||||
private close() {
|
||||
@@ -264,7 +310,7 @@ export default class PushNotificationPermission extends Vue {
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Browser Notifications Not Supported",
|
||||
title: "Browser Notifications Are Not Supported",
|
||||
text: "This browser does not support notifications.",
|
||||
},
|
||||
3000,
|
||||
@@ -329,7 +375,7 @@ export default class PushNotificationPermission extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
public async turnOnNotifications() {
|
||||
private async turnOnNotifications() {
|
||||
let notifyCloser = () => {};
|
||||
return this.askPermission()
|
||||
.then((permission) => {
|
||||
@@ -378,7 +424,8 @@ export default class PushNotificationPermission extends Vue {
|
||||
|
||||
const subscriptionWithTime: PushSubscriptionWithTime = {
|
||||
notifyTime: { utcHour: finalUtcHour, minute: finalUtcMinute },
|
||||
notifyType: "DAILY_CHECK",
|
||||
notifyType: this.pushType,
|
||||
message: this.messageInput,
|
||||
...subscription.toJSON(),
|
||||
};
|
||||
await this.sendSubscriptionToServer(subscriptionWithTime);
|
||||
@@ -398,23 +445,21 @@ export default class PushNotificationPermission extends Vue {
|
||||
);
|
||||
await libsUtil.sendTestThroughPushServer(subscription, true);
|
||||
notifyCloser();
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Notifications Turned On",
|
||||
text: "Notifications are on. You should see at least one on your device; if not, check the 'Troubleshoot' page.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
setTimeout(() => {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Notification Is On",
|
||||
text: "You should see at least one on your device; if not, check the 'Troubleshoot' link.",
|
||||
},
|
||||
7000,
|
||||
);
|
||||
}, 500);
|
||||
const timeText =
|
||||
// eslint-disable-next-line
|
||||
this.hourInput + ":" + this.minuteInput + " " + (this.hourAm ? "AM" : "PM");
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
notifyingNewActivity: true,
|
||||
notifyingNewActivityTime: timeText,
|
||||
});
|
||||
this.callback(true, timeText);
|
||||
this.callback(true, timeText, this.messageInput);
|
||||
})
|
||||
.catch((error) => {
|
||||
logConsoleAndDb(
|
||||
@@ -434,14 +479,7 @@ export default class PushNotificationPermission extends Vue {
|
||||
},
|
||||
3000,
|
||||
);
|
||||
// unsubscribe just in case we failed after getting a subscription
|
||||
navigator.serviceWorker?.ready
|
||||
.then((registration) => registration.pushManager.getSubscription())
|
||||
.then((subscription) => {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
// if we want to also unsubscribe, be sure to do that only if no other notification is active
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ export enum AppString {
|
||||
TEST_IMAGE_API_SERVER = "https://test-image-api.timesafari.app",
|
||||
LOCAL_IMAGE_API_SERVER = "http://localhost:3001",
|
||||
|
||||
PROD_PARTNER_API_SERVER = "https://endorser.ch",
|
||||
TEST_PARTNER_API_SERVER = "https://test-partner.endorser.ch",
|
||||
PROD_PARTNER_API_SERVER = "https://partner-api.endorser.ch",
|
||||
TEST_PARTNER_API_SERVER = "https://test-partner-api.endorser.ch",
|
||||
LOCAL_PARTNER_API_SERVER = LOCAL_ENDORSER_API_SERVER,
|
||||
|
||||
PROD_PUSH_SERVER = "https://timesafari.app",
|
||||
|
||||
@@ -39,8 +39,11 @@ export type Settings = {
|
||||
lastNotifiedClaimId?: string;
|
||||
lastViewedClaimId?: string;
|
||||
|
||||
notifyingNewActivity?: boolean; // set if they have turned on daily check for new activity via the push server
|
||||
notifyingNewActivityTime?: string; // set to their chosen time if they have turned on daily check for new activity via the push server
|
||||
notifyingReminderMessage?: string; // set to their chosen message for a daily reminder
|
||||
notifyingReminderTime?: string; // set to their chosen time for a daily reminder
|
||||
|
||||
partnerApiServer?: string; // partner server API URL
|
||||
|
||||
passkeyExpirationMinutes?: number; // passkey access token time-to-live in minutes
|
||||
|
||||
|
||||
@@ -364,6 +364,11 @@ export const getPasskeyExpirationSeconds = async (): Promise<number> => {
|
||||
);
|
||||
};
|
||||
|
||||
// These are shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
|
||||
export const DAILY_CHECK_TITLE = "DAILY_CHECK";
|
||||
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
|
||||
export const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
|
||||
|
||||
export const sendTestThroughPushServer = async (
|
||||
subscriptionJSON: PushSubscriptionJSON,
|
||||
skipFilter: boolean,
|
||||
@@ -374,16 +379,12 @@ export const sendTestThroughPushServer = async (
|
||||
pushUrl = settings.webPushServer;
|
||||
}
|
||||
|
||||
// This is shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
|
||||
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
|
||||
// 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 newPayload = {
|
||||
...subscriptionJSON,
|
||||
// ... overridden with the following
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
message: `Test, where you will see this message ${ skipFilter ? "un" : "" }filtered.`,
|
||||
title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push",
|
||||
...subscriptionJSON,
|
||||
};
|
||||
console.log("Sending a test web push message:", newPayload);
|
||||
const payloadStr = JSON.stringify(newPayload);
|
||||
|
||||
@@ -184,7 +184,36 @@
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- label -->
|
||||
<div>
|
||||
New Activity Notifications
|
||||
Reminder Notification
|
||||
<fa
|
||||
icon="question-circle"
|
||||
class="text-slate-400 fa-fw ml-2 cursor-pointer"
|
||||
@click.stop="showReminderNotificationInfo"
|
||||
/>
|
||||
</div>
|
||||
<!-- toggle -->
|
||||
<div
|
||||
class="relative ml-2 cursor-pointer"
|
||||
@click="showReminderNotificationChoice()"
|
||||
>
|
||||
<!-- input -->
|
||||
<input type="checkbox" v-model="notifyingReminder" class="sr-only" />
|
||||
<!-- line -->
|
||||
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
||||
<!-- dot -->
|
||||
<div
|
||||
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="notifyingReminder" class="w-full flex justify-between">
|
||||
<span class="ml-8 mr-8">Message: "{{ notifyingReminderMessage }}"</span>
|
||||
<span>{{ notifyingReminderTime.replace(" ", " ") }}</span>
|
||||
</div>
|
||||
<div class="mt-2 flex items-center justify-between">
|
||||
<!-- label -->
|
||||
<div>
|
||||
New Activity Notification
|
||||
<fa
|
||||
icon="question-circle"
|
||||
class="text-slate-400 fa-fw ml-2 cursor-pointer"
|
||||
@@ -200,7 +229,6 @@
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="notifyingNewActivity"
|
||||
name="toggleNotificationsInput"
|
||||
class="sr-only"
|
||||
/>
|
||||
<!-- line -->
|
||||
@@ -212,7 +240,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="notifyingNewActivityTime" class="w-full text-right">
|
||||
{{ notifyingNewActivityTime }}
|
||||
{{ notifyingNewActivityTime.replace(" ", " ") }}
|
||||
</div>
|
||||
<router-link class="pl-4 text-sm text-blue-500" to="/help-notifications">
|
||||
Troubleshoot your notification setup.
|
||||
@@ -609,18 +637,51 @@
|
||||
{{ DEFAULT_PUSH_SERVER }}
|
||||
</span>
|
||||
|
||||
<h2 class="text-slate-500 text-sm font-bold mb-2">Partner Server URL</h2>
|
||||
<div class="px-3 py-4">
|
||||
<input
|
||||
type="text"
|
||||
class="block w-full rounded border border-slate-400 px-3 py-2"
|
||||
v-model="partnerApiServerInput"
|
||||
/>
|
||||
<button
|
||||
v-if="partnerApiServerInput != partnerApiServer"
|
||||
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
|
||||
@click="onClickSavePartnerServer()"
|
||||
>
|
||||
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
|
||||
</button>
|
||||
<button
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="partnerApiServerInput = AppConstants.PROD_PARTNER_API_SERVER"
|
||||
>
|
||||
Use Prod
|
||||
</button>
|
||||
<button
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="partnerApiServerInput = AppConstants.TEST_PARTNER_API_SERVER"
|
||||
>
|
||||
Use Test
|
||||
</button>
|
||||
<button
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="partnerApiServerInput = AppConstants.LOCAL_PARTNER_API_SERVER"
|
||||
>
|
||||
Use Local
|
||||
</button>
|
||||
</div>
|
||||
<span class="px-4 text-sm" v-if="!partnerApiServerInput">
|
||||
When that setting is blank, this app will use the default partner server
|
||||
URL:
|
||||
{{ DEFAULT_PARTNER_API_SERVER }}
|
||||
</span>
|
||||
|
||||
<div id="sectionImageServerURL" class="mt-2">
|
||||
<span class="text-slate-500 text-sm font-bold">Image Server URL</span>
|
||||
|
||||
<span class="text-sm">{{ DEFAULT_IMAGE_API_SERVER }}</span>
|
||||
</div>
|
||||
|
||||
<div id="sectionPartnerServerURL" class="mt-2">
|
||||
<span class="text-slate-500 text-sm font-bold">Partner Server URL</span>
|
||||
|
||||
<span class="text-sm">{{ DEFAULT_PARTNER_API_SERVER }}</span>
|
||||
</div>
|
||||
|
||||
<label
|
||||
for="toggleHideRegisterPromptOnNewContact"
|
||||
class="flex items-center justify-between cursor-pointer mt-4"
|
||||
@@ -778,7 +839,7 @@ import {
|
||||
ImageRateLimits,
|
||||
tokenExpiryTimeDescription,
|
||||
} from "@/libs/endorserServer";
|
||||
import { getAccount } from "@/libs/util";
|
||||
import { DAILY_CHECK_TITLE, DIRECT_PUSH_TITLE, getAccount } from "@/libs/util";
|
||||
|
||||
const inputImportFileNameRef = ref<Blob>();
|
||||
|
||||
@@ -815,6 +876,11 @@ export default class AccountViewView extends Vue {
|
||||
loadingLimits = false;
|
||||
notifyingNewActivity = false;
|
||||
notifyingNewActivityTime = "";
|
||||
notifyingReminder = false;
|
||||
notifyingReminderMessage = "";
|
||||
notifyingReminderTime = "";
|
||||
partnerApiServer = "";
|
||||
partnerApiServerInput = "";
|
||||
passkeyExpirationDescription = "";
|
||||
passkeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
|
||||
previousPasskeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
|
||||
@@ -858,7 +924,7 @@ export default class AccountViewView extends Vue {
|
||||
const registration = await navigator.serviceWorker?.ready;
|
||||
this.subscription = await registration.pushManager.getSubscription();
|
||||
if (!this.subscription) {
|
||||
if (this.notifyingNewActivity) {
|
||||
if (this.notifyingNewActivity || this.notifyingReminder) {
|
||||
// the app thought there was a subscription but there isn't, so fix the settings
|
||||
this.turnOffNotifyingFlags();
|
||||
}
|
||||
@@ -909,14 +975,19 @@ export default class AccountViewView extends Vue {
|
||||
this.givenName =
|
||||
(settings?.firstName || "") +
|
||||
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
|
||||
this.isRegistered = !!settings?.isRegistered;
|
||||
this.imageServer = settings.imageServer || "";
|
||||
this.profileImageUrl = settings.profileImageUrl;
|
||||
this.showContactGives = !!settings.showContactGivesInline;
|
||||
this.hideRegisterPromptOnNewContact =
|
||||
!!settings.hideRegisterPromptOnNewContact;
|
||||
this.notifyingNewActivity = !!settings.notifyingNewActivity;
|
||||
this.isRegistered = !!settings?.isRegistered;
|
||||
this.imageServer = settings.imageServer || "";
|
||||
this.notifyingNewActivity = !!settings.notifyingNewActivityTime;
|
||||
this.notifyingNewActivityTime = settings.notifyingNewActivityTime || "";
|
||||
this.notifyingReminder = !!settings.notifyingReminderTime;
|
||||
this.notifyingReminderMessage = settings.notifyingReminderMessage || "";
|
||||
this.notifyingReminderTime = settings.notifyingReminderTime || "";
|
||||
this.partnerApiServer = settings.partnerApiServer || "";
|
||||
this.partnerApiServerInput = settings.partnerApiServer || "";
|
||||
this.profileImageUrl = settings.profileImageUrl;
|
||||
this.showContactGives = !!settings.showContactGivesInline;
|
||||
this.passkeyExpirationMinutes =
|
||||
settings.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES;
|
||||
this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes;
|
||||
@@ -1025,10 +1096,13 @@ export default class AccountViewView extends Vue {
|
||||
if (!this.notifyingNewActivity) {
|
||||
(
|
||||
this.$refs.pushNotificationPermission as PushNotificationPermission
|
||||
).open((success: boolean, time: string) => {
|
||||
).open(DAILY_CHECK_TITLE, async (success: boolean, timeText: string) => {
|
||||
if (success) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
notifyingNewActivityTime: timeText,
|
||||
});
|
||||
this.notifyingNewActivity = true;
|
||||
this.notifyingNewActivityTime = time;
|
||||
this.notifyingNewActivityTime = timeText;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -1036,10 +1110,13 @@ export default class AccountViewView extends Vue {
|
||||
{
|
||||
group: "modal",
|
||||
type: "notification-off",
|
||||
title: "", // unused, only here to satisfy type check
|
||||
title: DAILY_CHECK_TITLE, // repurposed to indicate the type of notification
|
||||
text: "", // unused, only here to satisfy type check
|
||||
callback: async (success) => {
|
||||
if (success) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
notifyingNewActivityTime: "",
|
||||
});
|
||||
this.notifyingNewActivity = false;
|
||||
this.notifyingNewActivityTime = "";
|
||||
}
|
||||
@@ -1050,6 +1127,71 @@ export default class AccountViewView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
async showReminderNotificationInfo() {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Reminder Notification",
|
||||
text: `
|
||||
This will notify you at a specific time each day.
|
||||
Note that it does not give you personalized notifications,
|
||||
so if you want less reliable but personalized notification then choose a 'New Activity' Notification.
|
||||
Do you want more details?
|
||||
`,
|
||||
onYes: async () => {
|
||||
await (this.$router as Router).push({
|
||||
name: "help-notification-types",
|
||||
});
|
||||
},
|
||||
yesText: "tell me more.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
|
||||
async showReminderNotificationChoice() {
|
||||
if (!this.notifyingReminder) {
|
||||
(
|
||||
this.$refs.pushNotificationPermission as PushNotificationPermission
|
||||
).open(
|
||||
DIRECT_PUSH_TITLE,
|
||||
async (success: boolean, timeText: string, message?: string) => {
|
||||
if (success) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
notifyingReminderMessage: message,
|
||||
notifyingReminderTime: timeText,
|
||||
});
|
||||
this.notifyingReminder = true;
|
||||
this.notifyingReminderMessage = message || "";
|
||||
this.notifyingReminderTime = timeText;
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "notification-off",
|
||||
title: DIRECT_PUSH_TITLE, // repurposed to indicate the type of notification
|
||||
text: "", // unused, only here to satisfy type check
|
||||
callback: async (success) => {
|
||||
if (success) {
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
notifyingReminderMessage: "",
|
||||
notifyingReminderTime: "",
|
||||
});
|
||||
this.notifyingReminder = false;
|
||||
this.notifyingReminderMessage = "";
|
||||
this.notifyingReminderTime = "";
|
||||
}
|
||||
},
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async toggleHideRegisterPromptOnNewContact() {
|
||||
const newSetting = !this.hideRegisterPromptOnNewContact;
|
||||
await db.open();
|
||||
@@ -1069,13 +1211,18 @@ export default class AccountViewView extends Vue {
|
||||
}
|
||||
|
||||
public async turnOffNotifyingFlags() {
|
||||
// should tell the push server as well
|
||||
await db.open();
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
notifyingNewActivity: false,
|
||||
notifyingNewActivityTime: "",
|
||||
notifyingReminderMessage: "",
|
||||
notifyingReminderTime: "",
|
||||
});
|
||||
this.notifyingNewActivity = false;
|
||||
this.notifyingNewActivityTime = "";
|
||||
this.notifyingReminder = false;
|
||||
this.notifyingReminderMessage = "";
|
||||
this.notifyingReminderTime = "";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1424,6 +1571,14 @@ export default class AccountViewView extends Vue {
|
||||
this.apiServer = this.apiServerInput;
|
||||
}
|
||||
|
||||
async onClickSavePartnerServer() {
|
||||
await db.open();
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
partnerApiServer: this.partnerApiServerInput,
|
||||
});
|
||||
this.partnerApiServer = this.partnerApiServerInput;
|
||||
}
|
||||
|
||||
async onClickSavePushServer() {
|
||||
await db.open();
|
||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<div class="overflow-hidden">
|
||||
<div class="text-sm">
|
||||
<div>
|
||||
<fa icon="arrow-down" class="fa-fw text-slate-400" />
|
||||
<fa icon="arrow-left" class="fa-fw text-slate-400" />
|
||||
{{ giverName }}
|
||||
</div>
|
||||
<div class="ml-6">gave</div>
|
||||
@@ -84,7 +84,7 @@
|
||||
</div>
|
||||
<div class="ml-6">to</div>
|
||||
<div>
|
||||
<fa icon="arrow-up" class="fa-fw text-slate-400" />
|
||||
<fa icon="arrow-right" class="fa-fw text-slate-400" />
|
||||
{{ recipientName }}
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -39,6 +39,15 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold mt-4">Android Users</h2>
|
||||
<div>
|
||||
<p>
|
||||
Note that you may not receive notifications when the app is in the
|
||||
background. When you're done working, close the app, and then you'll
|
||||
get the reminder notifications.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold mt-4">
|
||||
If this app doesn't support notifications...
|
||||
<!-- Note that that exact verbiage shows in a message elsewhere. -->
|
||||
|
||||
@@ -151,10 +151,12 @@
|
||||
<input type="checkbox" class="mr-2" v-model="sendToTrustroots" />
|
||||
<label>Send to Trustroots</label>
|
||||
</div>
|
||||
<!--
|
||||
<div class="flex" @click="sendToTripHopping = !sendToTripHopping">
|
||||
<input type="checkbox" class="mr-2" v-model="sendToTripHopping" />
|
||||
<label>Send to TripHopping</label>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
@@ -581,7 +583,12 @@ export default class NewEditProjectView extends Vue {
|
||||
);
|
||||
const nostrPubKey = pubPri?.publicKey;
|
||||
|
||||
const trustrootsUrl = DEFAULT_PARTNER_API_SERVER + "/api/partner/link";
|
||||
let partnerServer = DEFAULT_PARTNER_API_SERVER;
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
if (settings.partnerApiServer) {
|
||||
partnerServer = settings.partnerApiServer;
|
||||
}
|
||||
const trustrootsUrl = partnerServer + "/api/partner/link";
|
||||
const timeSafariUrl = window.location.origin + "/claim/" + jwtId;
|
||||
const content = this.fullClaim.name + " - see " + timeSafariUrl;
|
||||
// Why does IntelliJ not see matching types?
|
||||
|
||||
@@ -81,7 +81,7 @@ self.addEventListener("push", function (event) {
|
||||
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";
|
||||
title = "Direct Message";
|
||||
message = payload.message || "No details were provided.";
|
||||
} else {
|
||||
// any other title will run through regular filtering logic
|
||||
|
||||
8
test-playwright/LICENSE
Normal file
8
test-playwright/LICENSE
Normal file
@@ -0,0 +1,8 @@
|
||||
The author disclaims copyright to this source code. In place of a legal notice, here is a blessing:
|
||||
|
||||
May you do good and not evil.
|
||||
May you find forgiveness for yourself and forgive others.
|
||||
May you share freely, never taking more than you give.
|
||||
|
||||
________________________________________________________________
|
||||
from https://www.sqlite.org/src/info/689401a6cfb4c234 and memorialized here https://spdx.org/licenses/blessing.html
|
||||
Reference in New Issue
Block a user