parent
8d57cfc886
commit
bae4ad7d4a
|
@ -1,6 +1,7 @@
|
||||||
import type {
|
import type {
|
||||||
CreatePushSubscriptionParams,
|
CreatePushSubscriptionParams,
|
||||||
PushSubscription as MastoPushSubscription,
|
PushSubscription as MastoPushSubscription,
|
||||||
|
SubscriptionPolicy,
|
||||||
} from 'masto'
|
} from 'masto'
|
||||||
import type {
|
import type {
|
||||||
CreatePushNotification,
|
CreatePushNotification,
|
||||||
|
@ -8,11 +9,13 @@ import type {
|
||||||
RequiredUserLogin,
|
RequiredUserLogin,
|
||||||
} from '~/composables/push-notifications/types'
|
} from '~/composables/push-notifications/types'
|
||||||
import { useMasto } from '~/composables/masto'
|
import { useMasto } from '~/composables/masto'
|
||||||
import { currentUser, removePushNotifications } from '~/composables/users'
|
import { currentUser, removePushNotificationData, removePushNotifications } from '~/composables/users'
|
||||||
|
|
||||||
export const createPushSubscription = async (
|
export const createPushSubscription = async (
|
||||||
user: RequiredUserLogin,
|
user: RequiredUserLogin,
|
||||||
notificationData: CreatePushNotification,
|
notificationData: CreatePushNotification,
|
||||||
|
policy: SubscriptionPolicy = 'all',
|
||||||
|
force = false,
|
||||||
): Promise<MastoPushSubscription | undefined> => {
|
): Promise<MastoPushSubscription | undefined> => {
|
||||||
const { server: serverEndpoint, vapidKey } = user
|
const { server: serverEndpoint, vapidKey } = user
|
||||||
|
|
||||||
|
@ -26,19 +29,21 @@ export const createPushSubscription = async (
|
||||||
// If the VAPID public key did not change and the endpoint corresponds
|
// If the VAPID public key did not change and the endpoint corresponds
|
||||||
// to the endpoint saved in the backend, the subscription is valid
|
// to the endpoint saved in the backend, the subscription is valid
|
||||||
// If push subscription is not there, we need to create it: it is fetched on login
|
// If push subscription is not there, we need to create it: it is fetched on login
|
||||||
if (subscriptionServerKey === currentServerKey && subscription.endpoint === serverEndpoint && user.pushSubscription) {
|
if (subscriptionServerKey === currentServerKey && subscription.endpoint === serverEndpoint && (!force && user.pushSubscription)) {
|
||||||
return Promise.resolve(user.pushSubscription)
|
return Promise.resolve(user.pushSubscription)
|
||||||
}
|
}
|
||||||
else if (user.pushSubscription) {
|
else if (user.pushSubscription) {
|
||||||
// if we have a subscription, but it is not valid, we need to remove it
|
// if we have a subscription, but it is not valid or forcing renew, we need to remove it
|
||||||
return unsubscribeFromBackend(false)
|
// we need to prevent removing push notification data
|
||||||
|
return unsubscribeFromBackend(false, false)
|
||||||
|
.catch(removePushNotificationDataOnError)
|
||||||
.then(() => subscribe(registration, vapidKey))
|
.then(() => subscribe(registration, vapidKey))
|
||||||
.then(subscription => sendSubscriptionToBackend(subscription, notificationData))
|
.then(subscription => sendSubscriptionToBackend(subscription, notificationData, policy))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return subscribe(registration, vapidKey).then(
|
return subscribe(registration, vapidKey).then(
|
||||||
subscription => sendSubscriptionToBackend(subscription, notificationData),
|
subscription => sendSubscriptionToBackend(subscription, notificationData, policy),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -92,18 +97,30 @@ async function subscribe(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unsubscribeFromBackend(fromSWPushManager: boolean) {
|
async function unsubscribeFromBackend(fromSWPushManager: boolean, removePushNotification = true) {
|
||||||
|
const cu = currentUser.value
|
||||||
|
if (cu) {
|
||||||
|
await removePushNotifications(cu)
|
||||||
|
removePushNotification && await removePushNotificationData(cu, fromSWPushManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removePushNotificationDataOnError(e: Error) {
|
||||||
const cu = currentUser.value
|
const cu = currentUser.value
|
||||||
if (cu)
|
if (cu)
|
||||||
await removePushNotifications(cu, fromSWPushManager)
|
await removePushNotificationData(cu, true)
|
||||||
|
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendSubscriptionToBackend(
|
async function sendSubscriptionToBackend(
|
||||||
subscription: PushSubscription,
|
subscription: PushSubscription,
|
||||||
data: CreatePushNotification,
|
data: CreatePushNotification,
|
||||||
|
policy: SubscriptionPolicy,
|
||||||
): Promise<MastoPushSubscription> {
|
): Promise<MastoPushSubscription> {
|
||||||
const { endpoint, keys } = subscription.toJSON()
|
const { endpoint, keys } = subscription.toJSON()
|
||||||
const params: CreatePushSubscriptionParams = {
|
const params: CreatePushSubscriptionParams = {
|
||||||
|
policy,
|
||||||
subscription: {
|
subscription: {
|
||||||
endpoint: endpoint!,
|
endpoint: endpoint!,
|
||||||
keys: {
|
keys: {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { SubscriptionPolicy } from 'masto'
|
||||||
import type {
|
import type {
|
||||||
CreatePushNotification,
|
CreatePushNotification,
|
||||||
PushNotificationPolicy,
|
PushNotificationPolicy,
|
||||||
|
@ -14,6 +15,7 @@ const supportsPushNotifications = typeof window !== 'undefined'
|
||||||
&& 'getKey' in PushSubscription.prototype
|
&& 'getKey' in PushSubscription.prototype
|
||||||
|
|
||||||
export const usePushManager = () => {
|
export const usePushManager = () => {
|
||||||
|
const masto = useMasto()
|
||||||
const isSubscribed = ref(false)
|
const isSubscribed = ref(false)
|
||||||
const notificationPermission = ref<PermissionState | undefined>(
|
const notificationPermission = ref<PermissionState | undefined>(
|
||||||
Notification.permission === 'denied'
|
Notification.permission === 'denied'
|
||||||
|
@ -59,7 +61,7 @@ export const usePushManager = () => {
|
||||||
}
|
}
|
||||||
}, { immediate: true, flush: 'post' })
|
}, { immediate: true, flush: 'post' })
|
||||||
|
|
||||||
const subscribe = async (notificationData?: CreatePushNotification): Promise<SubscriptionResult> => {
|
const subscribe = async (notificationData?: CreatePushNotification, policy?: SubscriptionPolicy, force?: boolean): Promise<SubscriptionResult> => {
|
||||||
if (!isSupported || !currentUser.value)
|
if (!isSupported || !currentUser.value)
|
||||||
return 'invalid-state'
|
return 'invalid-state'
|
||||||
|
|
||||||
|
@ -90,9 +92,11 @@ export const usePushManager = () => {
|
||||||
return 'notification-denied'
|
return 'notification-denied'
|
||||||
}
|
}
|
||||||
|
|
||||||
currentUser.value.pushSubscription = await createPushSubscription({
|
currentUser.value.pushSubscription = await createPushSubscription(
|
||||||
|
{
|
||||||
pushSubscription, server, token, vapidKey,
|
pushSubscription, server, token, vapidKey,
|
||||||
}, notificationData ?? {
|
},
|
||||||
|
notificationData ?? {
|
||||||
alerts: {
|
alerts: {
|
||||||
follow: true,
|
follow: true,
|
||||||
favourite: true,
|
favourite: true,
|
||||||
|
@ -100,8 +104,10 @@ export const usePushManager = () => {
|
||||||
mention: true,
|
mention: true,
|
||||||
poll: true,
|
poll: true,
|
||||||
},
|
},
|
||||||
policy: 'all',
|
},
|
||||||
})
|
policy ?? 'all',
|
||||||
|
force,
|
||||||
|
)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
notificationPermission.value = permission
|
notificationPermission.value = permission
|
||||||
hiddenNotification.value[acct] = true
|
hiddenNotification.value[acct] = true
|
||||||
|
@ -116,9 +122,17 @@ export const usePushManager = () => {
|
||||||
await removePushNotifications(currentUser.value)
|
await removePushNotifications(currentUser.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveSettings = async () => {
|
const saveSettings = async (policy?: SubscriptionPolicy) => {
|
||||||
|
if (policy)
|
||||||
|
pushNotificationData.value.policy = policy
|
||||||
|
|
||||||
commit()
|
commit()
|
||||||
|
|
||||||
|
if (policy)
|
||||||
|
configuredPolicy.value[currentUser.value!.account.acct ?? ''] = policy
|
||||||
|
else
|
||||||
configuredPolicy.value[currentUser.value!.account.acct ?? ''] = pushNotificationData.value.policy
|
configuredPolicy.value[currentUser.value!.account.acct ?? ''] = pushNotificationData.value.policy
|
||||||
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
clear()
|
clear()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
@ -140,8 +154,8 @@ export const usePushManager = () => {
|
||||||
|
|
||||||
const updateSubscription = async () => {
|
const updateSubscription = async () => {
|
||||||
if (currentUser.value) {
|
if (currentUser.value) {
|
||||||
currentUser.value.pushSubscription = await useMasto().pushSubscriptions.update({
|
const previous = history.value[0].snapshot
|
||||||
data: {
|
const data = {
|
||||||
alerts: {
|
alerts: {
|
||||||
follow: pushNotificationData.value.follow,
|
follow: pushNotificationData.value.follow,
|
||||||
favourite: pushNotificationData.value.favourite,
|
favourite: pushNotificationData.value.favourite,
|
||||||
|
@ -149,10 +163,22 @@ export const usePushManager = () => {
|
||||||
mention: pushNotificationData.value.mention,
|
mention: pushNotificationData.value.mention,
|
||||||
poll: pushNotificationData.value.poll,
|
poll: pushNotificationData.value.poll,
|
||||||
},
|
},
|
||||||
policy: pushNotificationData.value.policy,
|
}
|
||||||
},
|
|
||||||
})
|
const policy = pushNotificationData.value.policy
|
||||||
await saveSettings()
|
|
||||||
|
const policyChanged = previous.policy !== policy
|
||||||
|
|
||||||
|
// to change policy we need to resubscribe
|
||||||
|
if (policyChanged)
|
||||||
|
await subscribe(data, policy, true)
|
||||||
|
else
|
||||||
|
currentUser.value.pushSubscription = await masto.pushSubscriptions.update({ data })
|
||||||
|
|
||||||
|
policyChanged && await nextTick()
|
||||||
|
|
||||||
|
// force change policy when changed: watch is resetting it on push subscription update
|
||||||
|
await saveSettings(policyChanged ? policy : undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,17 +96,7 @@ async function loginTo(user?: Omit<UserLogin, 'account'> & { account?: AccountCr
|
||||||
return masto
|
return masto
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removePushNotifications(user: UserLogin, fromSWPushManager = true) {
|
export async function removePushNotificationData(user: UserLogin, fromSWPushManager = true) {
|
||||||
if (!useRuntimeConfig().public.pwaEnabled || !user.pushSubscription)
|
|
||||||
return
|
|
||||||
|
|
||||||
// unsubscribe push notifications
|
|
||||||
try {
|
|
||||||
await useMasto().pushSubscriptions.remove()
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
// clear push subscription
|
// clear push subscription
|
||||||
user.pushSubscription = undefined
|
user.pushSubscription = undefined
|
||||||
const { acct } = user.account
|
const { acct } = user.account
|
||||||
|
@ -130,6 +120,19 @@ export async function removePushNotifications(user: UserLogin, fromSWPushManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function removePushNotifications(user: UserLogin) {
|
||||||
|
if (!useRuntimeConfig().public.pwaEnabled || !user.pushSubscription)
|
||||||
|
return
|
||||||
|
|
||||||
|
// unsubscribe push notifications
|
||||||
|
try {
|
||||||
|
await useMasto().pushSubscriptions.remove()
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function signout() {
|
export async function signout() {
|
||||||
// TODO: confirm
|
// TODO: confirm
|
||||||
if (!currentUser.value)
|
if (!currentUser.value)
|
||||||
|
@ -149,6 +152,8 @@ export async function signout() {
|
||||||
|
|
||||||
await removePushNotifications(currentUser.value)
|
await removePushNotifications(currentUser.value)
|
||||||
|
|
||||||
|
await removePushNotificationData(currentUser.value)
|
||||||
|
|
||||||
currentUserId.value = ''
|
currentUserId.value = ''
|
||||||
// Remove the current user from the users
|
// Remove the current user from the users
|
||||||
users.value.splice(index, 1)
|
users.value.splice(index, 1)
|
||||||
|
|
|
@ -6,7 +6,7 @@ const isPreview = process.env.PULL_REQUEST === 'true'
|
||||||
const pwa: VitePWANuxtOptions = {
|
const pwa: VitePWANuxtOptions = {
|
||||||
mode: isCI ? 'production' : 'development',
|
mode: isCI ? 'production' : 'development',
|
||||||
// disable PWA only when in preview mode
|
// disable PWA only when in preview mode
|
||||||
disable: isPreview || (isDevelopment && process.env.VITE_DEV_PWA !== 'true'),
|
disable: /* temporarily test in CI isPreview || */ (isDevelopment && process.env.VITE_DEV_PWA !== 'true'),
|
||||||
scope: '/',
|
scope: '/',
|
||||||
srcDir: './service-worker',
|
srcDir: './service-worker',
|
||||||
filename: 'sw.ts',
|
filename: 'sw.ts',
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lru-cache": "^7.14.1",
|
"lru-cache": "^7.14.1",
|
||||||
"masto": "^4.7.5",
|
"masto": "^4.11.1",
|
||||||
"pinia": "^2.0.27",
|
"pinia": "^2.0.27",
|
||||||
"shiki": "^0.11.1",
|
"shiki": "^0.11.1",
|
||||||
"shiki-es": "^0.1.2",
|
"shiki-es": "^0.1.2",
|
||||||
|
|
|
@ -45,7 +45,7 @@ specifiers:
|
||||||
jsdom: ^20.0.3
|
jsdom: ^20.0.3
|
||||||
lint-staged: ^13.0.4
|
lint-staged: ^13.0.4
|
||||||
lru-cache: ^7.14.1
|
lru-cache: ^7.14.1
|
||||||
masto: ^4.7.5
|
masto: ^4.11.1
|
||||||
nuxt: ^3.0.0
|
nuxt: ^3.0.0
|
||||||
pinia: ^2.0.27
|
pinia: ^2.0.27
|
||||||
postcss-nested: ^6.0.0
|
postcss-nested: ^6.0.0
|
||||||
|
@ -91,7 +91,7 @@ dependencies:
|
||||||
fuse.js: 6.6.2
|
fuse.js: 6.6.2
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
lru-cache: 7.14.1
|
lru-cache: 7.14.1
|
||||||
masto: 4.7.5
|
masto: 4.11.1
|
||||||
pinia: 2.0.27_typescript@4.9.3
|
pinia: 2.0.27_typescript@4.9.3
|
||||||
shiki: 0.11.1
|
shiki: 0.11.1
|
||||||
shiki-es: 0.1.2
|
shiki-es: 0.1.2
|
||||||
|
@ -5474,6 +5474,11 @@ packages:
|
||||||
|
|
||||||
/eventemitter3/4.0.7:
|
/eventemitter3/4.0.7:
|
||||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/eventemitter3/5.0.0:
|
||||||
|
resolution: {integrity: sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/execa/5.1.1:
|
/execa/5.1.1:
|
||||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||||
|
@ -6900,12 +6905,12 @@ packages:
|
||||||
semver: 6.3.0
|
semver: 6.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/masto/4.7.5:
|
/masto/4.11.1:
|
||||||
resolution: {integrity: sha512-FRqr5yAAJm6PVCPqxrQt7uIwCft+FmPZgcjvLpzo4kzhgvBPGax70rFiGrULYZ34ehnrbcGOp4J+VcM1FxDU2w==}
|
resolution: {integrity: sha512-siTQNhfLV1JjOERCGgjagMvD6q0K0hLuhOXrbXNcYzHAwpbPeSeAM6CSpIRrZ8zFDepOR62Djs/GtJdTR21Rfw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
axios: 1.1.3
|
axios: 1.1.3
|
||||||
change-case: 4.1.2
|
change-case: 4.1.2
|
||||||
eventemitter3: 4.0.7
|
eventemitter3: 5.0.0
|
||||||
isomorphic-form-data: 2.0.0
|
isomorphic-form-data: 2.0.0
|
||||||
isomorphic-ws: 5.0.0_ws@8.11.0
|
isomorphic-ws: 5.0.0_ws@8.11.0
|
||||||
semver: 7.3.8
|
semver: 7.3.8
|
||||||
|
|
Loading…
Reference in New Issue