hippofish/packages/client/src/components/MkPushNotificationAllowButton.vue

215 lines
5 KiB
Vue
Raw Normal View History

<template>
2023-04-08 02:01:42 +02:00
<MkButton
v-if="supported && !pushRegistrationInServer"
type="button"
primary
:gradate="gradate"
:rounded="rounded"
:inline="inline"
:autofocus="autofocus"
:wait="wait"
:full="full"
@click="subscribe"
>
{{ i18n.ts.subscribePushNotification }}
</MkButton>
<MkButton
v-else-if="
!showOnlyToRegister &&
($i ? pushRegistrationInServer : pushSubscription)
"
type="button"
:primary="false"
:gradate="gradate"
:rounded="rounded"
:inline="inline"
:autofocus="autofocus"
:wait="wait"
:full="full"
@click="unsubscribe"
>
{{ i18n.ts.unsubscribePushNotification }}
</MkButton>
<MkButton
v-else-if="$i && pushRegistrationInServer"
disabled
:rounded="rounded"
:inline="inline"
:wait="wait"
:full="full"
>
{{ i18n.ts.pushNotificationAlreadySubscribed }}
</MkButton>
<MkButton
v-else-if="!supported"
disabled
:rounded="rounded"
:inline="inline"
:wait="wait"
:full="full"
>
{{ i18n.ts.pushNotificationNotSupported }}
</MkButton>
</template>
<script setup lang="ts">
import { ref } from "vue";
2023-04-08 02:01:42 +02:00
import { $i, getAccounts } from "@/account";
import MkButton from "@/components/MkButton.vue";
import { instance } from "@/instance";
import { api, apiWithDialog, promiseDialog } from "@/os";
import { i18n } from "@/i18n";
defineProps<{
primary?: boolean;
gradate?: boolean;
rounded?: boolean;
inline?: boolean;
link?: boolean;
to?: string;
autofocus?: boolean;
wait?: boolean;
danger?: boolean;
full?: boolean;
showOnlyToRegister?: boolean;
}>();
// ServiceWorker registration
2023-09-02 01:27:33 +02:00
const registration = ref<ServiceWorkerRegistration | undefined>();
// If this browser supports push notification
2023-09-02 01:27:33 +02:00
const supported = ref(false);
// If this browser has already subscribed to push notification
2023-09-02 01:27:33 +02:00
const pushSubscription = ref<PushSubscription | null>(null);
const pushRegistrationInServer = ref<
2023-04-08 02:01:42 +02:00
| {
state?: string;
key?: string;
userId: string;
endpoint: string;
sendReadMessage: boolean;
}
| undefined
>();
function subscribe() {
if (!registration.value || !supported.value || !instance.swPublickey)
return;
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
2023-04-08 02:01:42 +02:00
return promiseDialog(
registration.value.pushManager
2023-04-08 02:01:42 +02:00
.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
2023-07-06 03:28:27 +02:00
instance.swPublickey,
2023-04-08 02:01:42 +02:00
),
})
.then(
async (subscription) => {
pushSubscription.value = subscription;
2023-04-08 02:01:42 +02:00
// Register
pushRegistrationInServer.value = await api("sw/register", {
2023-04-08 02:01:42 +02:00
endpoint: subscription.endpoint,
auth: encode(subscription.getKey("auth")),
publickey: encode(subscription.getKey("p256dh")),
});
},
async (err) => {
// When subscribe failed
// 通知が許可されていなかったとき
if (err?.name === "NotAllowedError") {
console.info(
2023-07-06 03:28:27 +02:00
"User denied the notification permission request.",
2023-04-08 02:01:42 +02:00
);
return;
}
// 違うapplicationServerKey (または gcm_sender_id)のサブスクリプションが
// 既に存在していることが原因でエラーになった可能性があるので、
// そのサブスクリプションを解除しておく
// (これは実行されなさそうだけど、おまじない的に古い実装から残してある)
await unsubscribe();
2023-07-06 03:28:27 +02:00
},
2023-04-08 02:01:42 +02:00
),
null,
2023-07-06 03:28:27 +02:00
null,
2023-04-08 02:01:42 +02:00
);
}
async function unsubscribe() {
if (!pushSubscription.value) return;
const endpoint = pushSubscription.value.endpoint;
const accounts = await getAccounts();
pushRegistrationInServer.value = undefined;
if ($i && accounts.length >= 2) {
2023-04-08 02:01:42 +02:00
apiWithDialog("sw/unregister", {
i: $i.token,
endpoint,
});
} else {
pushSubscription.value.unsubscribe();
2023-04-08 02:01:42 +02:00
apiWithDialog("sw/unregister", {
endpoint,
});
pushSubscription.value = null;
}
}
function encode(buffer: ArrayBuffer | null) {
return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)));
}
/**
* Convert the URL safe base64 string to a Uint8Array
* @param base64String base64 string
*/
function urlBase64ToUint8Array(base64String: string): Uint8Array {
2023-04-08 02:01:42 +02:00
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
2023-04-08 02:01:42 +02:00
.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;
}
if (navigator.serviceWorker == null) {
// TODO: よしなに?
} else {
2023-04-08 02:01:42 +02:00
navigator.serviceWorker.ready.then(async (swr) => {
registration.value = swr;
pushSubscription.value =
await registration.value.pushManager.getSubscription();
2023-04-08 02:01:42 +02:00
if (instance.swPublickey && "PushManager" in window && $i && $i.token) {
supported.value = true;
if (pushSubscription.value) {
2023-04-08 02:01:42 +02:00
const res = await api("sw/show-registration", {
endpoint: pushSubscription.value.endpoint,
});
if (res) {
pushRegistrationInServer.value = res;
}
}
}
});
}
defineExpose({
2023-09-02 01:27:33 +02:00
pushRegistrationInServer,
});
</script>