Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions components/matchmaking/MatchmakingConfirm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ import { useMatchmakingStore } from "~/stores/MatchmakingStore";
import { useMatchReadyModal } from "~/composables/useMatchReadyModal";
import socket from "~/web-sockets/Socket";
import { useSound } from "~/composables/useSound";
import { closeNotifications } from "~/composables/usePushNotifications";
import { notificationThreadKey } from "~/utilities/chatThread";

export default {
data() {
Expand Down Expand Up @@ -183,6 +185,17 @@ export default {
confirmation: {
immediate: true,
handler(confirmation, oldConfirmation) {
if (
oldConfirmation &&
oldConfirmation.confirmationId !== confirmation?.confirmationId
) {
this.silenceRing(oldConfirmation.confirmationId);
}

if (confirmation?.isReady || confirmation?.matchId) {
this.silenceRing(confirmation.confirmationId);
}

if (!confirmation) {
useMatchReadyModal().closeMatchReadyModal();
return;
Expand Down Expand Up @@ -219,6 +232,14 @@ export default {
confirmationId: this.confirmation.confirmationId,
});
},
// On every update rather than once: the push is sent after the socket
// update that opened the ready check, so it can land here after the
// player has already accepted.
silenceRing(confirmationId: string) {
void closeNotifications(
notificationThreadKey("MatchFound", confirmationId),
);
},
updateCountdown() {
if (
this.confirmation?.expiresAt &&
Expand Down
36 changes: 34 additions & 2 deletions composables/useCameraTalkback.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { ref, onScopeDispose } from "vue";
import { ref, onScopeDispose, watch } from "vue";
import { useDocumentVisibility } from "@vueuse/core";
import socket from "~/web-sockets/Socket";
import {
cameraPlayerTalkUrl,
fetchCameraTalkStatus,
hangupPlayerTalk,
negotiateWebRtc,
} from "~/composables/useCameraApi";
import { useIceServers } from "~/composables/useIceServers";
import { closeNotifications } from "~/composables/usePushNotifications";
import { notificationThreadKey } from "~/utilities/chatThread";

// The other direction: an organizer talking to the player whose camera this is.
// Nothing here starts until the player has connected, because the connect click
Expand All @@ -22,8 +26,31 @@ export function useCameraTalkback(matchId: () => string) {
// clearing the timer does nothing to a request that is about to re-arm it, so
// the page kept polling -- and could still open a peer connection -- forever.
let disposed = false;
// Hanging up kicks every session on the talk path, the organizer's included.
// A tab that never connected -- opened from the ring on a device that is not
// the camera -- must not end a call the player is taking on the one that is.
let started = false;

const ice = useIceServers();
const visibility = useDocumentVisibility();

const callThread = () => notificationThreadKey("AdminCall", matchId());

// The camera page has no layout, so no chat presence reporter runs beside
// this one, and whatever the previous page last reported would otherwise
// keep going out on every heartbeat. While the call is playing here,
// reporting it as focused lets the server skip ringing the player again
// about a call they are already on.
watch(
[talking, visibility],
([isTalking, visible]) => {
socket.setPresence({
visible: visible !== "hidden",
focus: isTalking ? callThread() : null,
});
},
{ immediate: true },
);

// If autoplay refuses sound the promise rejects and nothing plays at all, so
// fall back to a muted start -- a picture with a visible unmute beats a black
Expand Down Expand Up @@ -68,6 +95,7 @@ export function useCameraTalkback(matchId: () => string) {

await negotiateWebRtc(pc, cameraPlayerTalkUrl(matchId()), "include");
talking.value = true;
void closeNotifications(callThread());
} catch {
end();
}
Expand Down Expand Up @@ -110,6 +138,7 @@ export function useCameraTalkback(matchId: () => string) {
}

function start() {
started = true;
void poll();
}

Expand All @@ -121,7 +150,10 @@ export function useCameraTalkback(matchId: () => string) {
}

end();
void hangupPlayerTalk(matchId());

if (started) {
void hangupPlayerTalk(matchId());
}
});

return { talkEl, talking, muted, start, toggleAudio, end };
Expand Down
27 changes: 27 additions & 0 deletions composables/usePushNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,33 @@ async function getExistingSubscription(): Promise<PushSubscription | null> {
return registration.pushManager.getSubscription();
}

// Takes a ring off this device once it has been answered here. Other devices
// keep theirs until their worker closes it at `expiresAt`.
//
// getRegistration() rather than serviceWorker.ready, which never settles when
// no worker is registered.
export async function closeNotifications(tag: string): Promise<void> {
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
return;
}

try {
const registration = await navigator.serviceWorker.getRegistration();

if (!registration || typeof registration.getNotifications !== "function") {
return;
}

const notifications = await registration.getNotifications({ tag });

for (const notification of notifications) {
notification.close();
}
} catch {
// Best effort: the worker closes it at `expiresAt` regardless.
}
}

export function usePushNotifications() {
const isDenied = computed(() => permission.value === "denied");

Expand Down
10 changes: 9 additions & 1 deletion i18n/locales/ar_SA.json
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,14 @@
"title": "المباريات",
"description": "تغيّرات حالة المباراة والمباريات المستوردة."
},
"match_found": {
"title": "تم العثور على مباراة",
"description": "وجد نظام المطابقة مباراتك وينتظر قبولك. يرن حتى خلال ساعات الهدوء."
},
"admin_call": {
"title": "مكالمات المشرفين",
"description": "مشرف يريد التحدث إليك عبر كاميرا المباراة. يرن حتى خلال ساعات الهدوء."
},
"chat": {
"title": "الدردشة",
"description": "رسائل جديدة في محادثة أو ردهة أو غرفة بطولة."
Expand Down Expand Up @@ -1824,7 +1832,7 @@
},
"quiet_hours": {
"title": "ساعات الهدوء",
"description": "تبقى الإشعارات الفورية صامتة خلال هذه الفترة، لكنها تظل تظهر في جرس التنبيهات.",
"description": "تبقى الإشعارات الفورية صامتة خلال هذه الفترة، لكنها تظل تظهر في جرس التنبيهات. أما العثور على مباراة ومكالمات المشرفين فترن على أي حال.",
"from": "من",
"to": "إلى",
"reset": "إعادة الضبط",
Expand Down
10 changes: 9 additions & 1 deletion i18n/locales/da_DK.json
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,14 @@
"title": "Kampe",
"description": "Ændringer i kampstatus og importerede kampe."
},
"match_found": {
"title": "Kamp fundet",
"description": "Matchmaking har fundet din kamp og venter på, at du accepterer. Ringer også i stilletimer."
},
"admin_call": {
"title": "Opkald fra admins",
"description": "En admin vil tale med dig via dit kampkamera. Ringer også i stilletimer."
},
"chat": {
"title": "Chat",
"description": "Nye beskeder i en samtale, en lobby eller et turneringsrum."
Expand Down Expand Up @@ -1824,7 +1832,7 @@
},
"quiet_hours": {
"title": "Stilletimer",
"description": "Push-notifikationer forbliver lydløse i dette tidsrum. De vises stadig i din notifikationsklokke.",
"description": "Push-notifikationer forbliver lydløse i dette tidsrum. De vises stadig i din notifikationsklokke. Fundne kampe og opkald fra admins ringer alligevel.",
"from": "Fra",
"to": "Til",
"reset": "Nulstil",
Expand Down
10 changes: 9 additions & 1 deletion i18n/locales/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,14 @@
"title": "Matches",
"description": "Änderungen des Matchstatus und importierte Matches."
},
"match_found": {
"title": "Match gefunden",
"description": "Das Matchmaking hat dein Match gefunden und wartet darauf, dass du annimmst. Klingelt auch während der Ruhezeiten."
},
"admin_call": {
"title": "Admin-Anrufe",
"description": "Ein Admin möchte über deine Match-Kamera mit dir sprechen. Klingelt auch während der Ruhezeiten."
},
"chat": {
"title": "Chat",
"description": "Neue Nachrichten in einer Unterhaltung, Lobby oder einem Turnierraum."
Expand Down Expand Up @@ -1824,7 +1832,7 @@
},
"quiet_hours": {
"title": "Ruhezeiten",
"description": "Push-Benachrichtigungen bleiben in diesem Zeitfenster stumm. In deiner Benachrichtigungsglocke erscheinen sie weiterhin.",
"description": "Push-Benachrichtigungen bleiben in diesem Zeitfenster stumm. In deiner Benachrichtigungsglocke erscheinen sie weiterhin. Gefundene Matches und Admin-Anrufe klingeln trotzdem.",
"from": "Von",
"to": "Bis",
"reset": "Zurücksetzen",
Expand Down
10 changes: 9 additions & 1 deletion i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,14 @@
"title": "Matches",
"description": "Match status changes and imported matches."
},
"match_found": {
"title": "Match found",
"description": "Matchmaking found your match and is waiting for you to accept. Rings even during quiet hours."
},
"admin_call": {
"title": "Admin calls",
"description": "An admin wants to talk to you over your match camera. Rings even during quiet hours."
},
"chat": {
"title": "Chat",
"description": "New messages in a conversation, lobby or tournament room."
Expand Down Expand Up @@ -1824,7 +1832,7 @@
},
"quiet_hours": {
"title": "Quiet Hours",
"description": "Push notifications stay silent during this window. They still appear in your alert bell.",
"description": "Push notifications stay silent during this window. They still appear in your alert bell. Match-found alerts and admin calls ring anyway.",
"from": "From",
"to": "To",
"reset": "Reset",
Expand Down
10 changes: 9 additions & 1 deletion i18n/locales/es_ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,14 @@
"title": "Partidas",
"description": "Cambios de estado de partida y partidas importadas."
},
"match_found": {
"title": "Partida encontrada",
"description": "El emparejamiento ha encontrado tu partida y espera a que la aceptes. Suena incluso en horas de silencio."
},
"admin_call": {
"title": "Llamadas de administradores",
"description": "Un administrador quiere hablar contigo a través de la cámara de tu partida. Suena incluso en horas de silencio."
},
"chat": {
"title": "Chat",
"description": "Mensajes nuevos en una conversación, un lobby o una sala de torneo."
Expand Down Expand Up @@ -1824,7 +1832,7 @@
},
"quiet_hours": {
"title": "Horas de silencio",
"description": "Las notificaciones push permanecen en silencio durante este intervalo. Siguen apareciendo en tu campana de avisos.",
"description": "Las notificaciones push permanecen en silencio durante este intervalo. Siguen apareciendo en tu campana de avisos. Las partidas encontradas y las llamadas de administradores suenan igualmente.",
"from": "Desde",
"to": "Hasta",
"reset": "Restablecer",
Expand Down
10 changes: 9 additions & 1 deletion i18n/locales/fr_FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,14 @@
"title": "Matchs",
"description": "Changements de statut de match et matchs importés."
},
"match_found": {
"title": "Match trouvé",
"description": "Le matchmaking a trouvé votre match et attend que vous acceptiez. Sonne même pendant les heures silencieuses."
},
"admin_call": {
"title": "Appels d'administrateurs",
"description": "Un administrateur veut vous parler via la caméra de votre match. Sonne même pendant les heures silencieuses."
},
"chat": {
"title": "Chat",
"description": "Nouveaux messages dans une conversation, un lobby ou un salon de tournoi."
Expand Down Expand Up @@ -1824,7 +1832,7 @@
},
"quiet_hours": {
"title": "Heures silencieuses",
"description": "Les notifications push restent silencieuses pendant cette plage. Elles apparaissent toujours dans votre cloche d'alertes.",
"description": "Les notifications push restent silencieuses pendant cette plage. Elles apparaissent toujours dans votre cloche d'alertes. Les matchs trouvés et les appels d'administrateurs sonnent quand même.",
"from": "De",
"to": "À",
"reset": "Réinitialiser",
Expand Down
10 changes: 9 additions & 1 deletion i18n/locales/it_IT.json
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,14 @@
"title": "Partite",
"description": "Cambi di stato delle partite e partite importate."
},
"match_found": {
"title": "Match trovato",
"description": "Il matchmaking ha trovato la tua partita e aspetta che tu accetti. Suona anche nelle ore di silenzio."
},
"admin_call": {
"title": "Chiamate degli admin",
"description": "Un admin vuole parlarti tramite la videocamera della partita. Suona anche nelle ore di silenzio."
},
"chat": {
"title": "Chat",
"description": "Nuovi messaggi in una conversazione, una lobby o una sala torneo."
Expand Down Expand Up @@ -1824,7 +1832,7 @@
},
"quiet_hours": {
"title": "Ore di silenzio",
"description": "Le notifiche push restano silenziose in questa fascia. Compaiono comunque nella tua campanella degli avvisi.",
"description": "Le notifiche push restano silenziose in questa fascia. Compaiono comunque nella tua campanella degli avvisi. Le partite trovate e le chiamate degli admin suonano in ogni caso.",
"from": "Da",
"to": "A",
"reset": "Reimposta",
Expand Down
10 changes: 9 additions & 1 deletion i18n/locales/ja_JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,14 @@
"title": "試合",
"description": "試合状況の変化とインポートされた試合。"
},
"match_found": {
"title": "マッチ成立",
"description": "マッチメイキングで試合が見つかり、承認を待っています。サイレント時間中も鳴ります。"
},
"admin_call": {
"title": "管理者からの呼び出し",
"description": "管理者が試合用カメラを通じてあなたと話したがっています。サイレント時間中も鳴ります。"
},
"chat": {
"title": "チャット",
"description": "会話、ロビー、トーナメントルームの新着メッセージ。"
Expand Down Expand Up @@ -1824,7 +1832,7 @@
},
"quiet_hours": {
"title": "サイレント時間",
"description": "この時間帯はプッシュ通知が鳴りません。通知ベルには引き続き表示されます。",
"description": "この時間帯はプッシュ通知が鳴りません。通知ベルには引き続き表示されます。マッチ成立と管理者からの呼び出しは例外として鳴ります。",
"from": "開始",
"to": "終了",
"reset": "リセット",
Expand Down
10 changes: 9 additions & 1 deletion i18n/locales/ko_KR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,14 @@
"title": "경기",
"description": "경기 상태 변경 및 가져온 경기."
},
"match_found": {
"title": "매치 찾음",
"description": "매치메이킹이 매치를 찾았고 수락을 기다리고 있습니다. 방해 금지 시간에도 울립니다."
},
"admin_call": {
"title": "관리자 호출",
"description": "관리자가 경기 카메라로 대화하고 싶어 합니다. 방해 금지 시간에도 울립니다."
},
"chat": {
"title": "채팅",
"description": "대화, 로비 또는 토너먼트 방의 새 메시지."
Expand Down Expand Up @@ -1824,7 +1832,7 @@
},
"quiet_hours": {
"title": "방해 금지 시간",
"description": "이 시간대에는 푸시 알림이 소리 없이 전달됩니다. 알림 종에는 계속 표시됩니다.",
"description": "이 시간대에는 푸시 알림이 소리 없이 전달됩니다. 알림 종에는 계속 표시됩니다. 매치 찾음과 관리자 호출은 예외로 울립니다.",
"from": "시작",
"to": "종료",
"reset": "초기화",
Expand Down
10 changes: 9 additions & 1 deletion i18n/locales/pl_PL.json
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,14 @@
"title": "Mecze",
"description": "Zmiany statusu meczu i zaimportowane mecze."
},
"match_found": {
"title": "Znaleziono mecz",
"description": "Matchmaking znalazł twój mecz i czeka, aż go zaakceptujesz. Dzwoni nawet w godzinach ciszy."
},
"admin_call": {
"title": "Połączenia od administratorów",
"description": "Administrator chce porozmawiać z tobą przez kamerę meczu. Dzwoni nawet w godzinach ciszy."
},
"chat": {
"title": "Czat",
"description": "Nowe wiadomości w rozmowie, lobby lub pokoju turniejowym."
Expand Down Expand Up @@ -1824,7 +1832,7 @@
},
"quiet_hours": {
"title": "Godziny ciszy",
"description": "W tym przedziale powiadomienia push pozostają wyciszone. Nadal pojawiają się w dzwonku powiadomień.",
"description": "W tym przedziale powiadomienia push pozostają wyciszone. Nadal pojawiają się w dzwonku powiadomień. Znalezione mecze i połączenia od administratorów dzwonią mimo to.",
"from": "Od",
"to": "Do",
"reset": "Resetuj",
Expand Down
Loading
Loading