-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: ensure djs clients are singleton instances * feat: add votes table * feat: vote notification * chore: reduce vote cooldown to 15 min * feat: add SetNotification to server * chore: add debug logs * fix: do not add notification when token and voteid already exists * feat: add loading indicator * feat: refresh notification when voted * feat: add opt-out * feat: add debug log * fix: initialize firebase app * fix: remove app on messaging * feat: show notifications only with service worker * fix: state improperly used * fix: schedule notification if notification is newly added * chore: remove duplicated notification * chore: add spacing * chore: get token if notification is granted * chore: change vote cooldown to 12 hours * chore: remove logging
- Loading branch information
1 parent
e99e661
commit 160fe4e
Showing
14 changed files
with
1,597 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,4 +43,7 @@ yarn-error.log* | |
package-lock.json | ||
|
||
# sub module | ||
api-docs/ | ||
api-docs/ | ||
|
||
# Firebase | ||
service-account.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
import Fetch from '@utils/Fetch' | ||
import { initializeApp } from 'firebase/app' | ||
import { getMessaging, getToken as getFirebaseToken } from 'firebase/messaging' | ||
import { useState } from 'react' | ||
import Button from './Button' | ||
|
||
export async function getFCMToken() { | ||
try { | ||
const app = initializeApp({ | ||
apiKey: 'AIzaSyDWnwXCBaP1C627gfIBQxyZbmNnAU_b_1Q', | ||
authDomain: 'koreanlist-e95d9.firebaseapp.com', | ||
projectId: 'koreanlist-e95d9', | ||
storageBucket: 'koreanlist-e95d9.firebasestorage.app', | ||
messagingSenderId: '716438516411', | ||
appId: '1:716438516411:web:cddd6c7cc3b0571fa4af9e', | ||
}) | ||
|
||
const worker = await navigator.serviceWorker.register('/vote-notification-sw.js') | ||
|
||
const messaging = getMessaging(app) | ||
const token = await getFirebaseToken(messaging, { | ||
vapidKey: process.env.NEXT_PUBLIC_FCM_VAPID_KEY, | ||
serviceWorkerRegistration: worker, | ||
}) | ||
return token | ||
} catch (e) { | ||
return null | ||
} | ||
} | ||
|
||
function SetNotification({ id, notificationSet }: { id: string; notificationSet: boolean }) { | ||
const [state, setState] = useState(notificationSet ? 1 : 0) | ||
const [hold, setHold] = useState(false) | ||
|
||
const getToken = async () => { | ||
if (!('serviceWorker' in navigator)) { | ||
setState(4) | ||
return 'NO_SERVICE_WORKER' | ||
} | ||
|
||
if (!('Notification' in window)) { | ||
setState(4) | ||
return 'NO_NOTIFICATION' | ||
} | ||
|
||
const p = await Notification.requestPermission() | ||
if (p !== 'granted') { | ||
setState(5) | ||
return 'PERMISSION_DENIED' | ||
} | ||
|
||
const token = await getFCMToken() | ||
|
||
if (!token) { | ||
setState(4) | ||
return | ||
} | ||
|
||
const result = await Fetch('/users/notification', { | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
token, | ||
targetId: id, | ||
}), | ||
}) | ||
|
||
if (result.code === 200) { | ||
setState(2) | ||
} else { | ||
setState(4) | ||
} | ||
} | ||
const components = { | ||
0: ( | ||
<> | ||
<p className='whitespace-pre-line text-lg font-normal'> | ||
12시간 후에 이 기기로 알림을 받으려면 아래 버튼을 눌러주세요. | ||
</p> | ||
<Button | ||
disabled={hold} | ||
onClick={() => { | ||
setHold(true) | ||
getToken() | ||
.then(() => { | ||
setHold(false) | ||
}) | ||
.catch(() => { | ||
setState(4) | ||
}) | ||
}} | ||
> | ||
<> | ||
<i className='far fa-bell' /> {hold ? '설정 중...' : '알림 설정'} | ||
</> | ||
</Button> | ||
</> | ||
), | ||
1: ( | ||
<> | ||
<p className='whitespace-pre-line text-lg font-normal'> | ||
이 기기로 알림을 수신하고 있습니다. 알림을 해제하려면 아래 버튼을 눌러주세요. | ||
</p> | ||
<Button | ||
disabled={hold} | ||
onClick={() => { | ||
setHold(true) | ||
getFCMToken() | ||
.then(async (token) => { | ||
await Fetch('/users/notification', { | ||
method: 'DELETE', | ||
body: JSON.stringify({ | ||
token, | ||
targetId: id, | ||
}), | ||
}) | ||
setHold(false) | ||
setState(3) | ||
}) | ||
.catch(() => { | ||
setState(4) | ||
}) | ||
}} | ||
> | ||
<> | ||
<i className='far fa-bell-slash mr-1' /> {hold ? '설정 중...' : '알림 해제'} | ||
</> | ||
</Button> | ||
</> | ||
), | ||
2: ( | ||
<> | ||
<p className='whitespace-pre-line text-lg font-normal'>알림이 설정되었습니다.</p> | ||
</> | ||
), | ||
3: ( | ||
<> | ||
<p className='whitespace-pre-line text-lg font-normal'>알림이 해제되었습니다.</p> | ||
</> | ||
), | ||
4: ( | ||
<> | ||
<p className='whitespace-pre-line text-lg font-normal'> | ||
알림을 설정할 수 없습니다. 사용하는 브라우저를 점검해주세요. {'\n'} | ||
iOS 사용자는 Safari 브라우저에서 한국 디스코드 리스트를 홈 화면에 추가해야 합니다. | ||
</p> | ||
</> | ||
), | ||
5: ( | ||
<> | ||
<p className='whitespace-pre-line text-lg font-normal'> | ||
알림이 허용되지 않았습니다. 브라우저 설정에서 알림을 허용해주세요. | ||
</p> | ||
</> | ||
), | ||
} | ||
return ( | ||
components[state] ?? ( | ||
<p className='whitespace-pre-line text-lg font-normal'> | ||
알림을 설정할 수 없습니다. 사용하는 브라우저를 점검해주세요. | ||
</p> | ||
) | ||
) | ||
} | ||
|
||
export default SetNotification |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { addNotification, get } from '@utils/Query' | ||
import RequestHandler from '@utils/RequestHandler' | ||
|
||
const Notification = RequestHandler() | ||
.get(async (req, res) => { | ||
const token = req.query.token as string | ||
const target = req.query.target as string | ||
const user = await get.Authorization(req.cookies.token) | ||
if (!user) return res.status(401).json({ code: 401 }) | ||
|
||
const result = token | ||
? await get.notifications.token(token, target) | ||
: await get.notifications.user(user) | ||
|
||
if (!result) return res.status(400).json({ code: 400 }) | ||
|
||
return res.status(200).json({ code: 200, data: result }) | ||
}) | ||
.post(async (req, res) => { | ||
const user = await get.Authorization(req.cookies.token) | ||
if (!user) return res.status(401).json({ code: 401 }) | ||
|
||
const { token, targetId } = req.body | ||
|
||
if (!token || !targetId) | ||
return res.status(400).json({ code: 400, message: 'Either token or targetId is missing' }) | ||
|
||
const result = await addNotification({ token, targetId, userId: user }) | ||
if (typeof result === 'string') return res.status(400).json({ code: 400, message: result }) | ||
|
||
return res.status(200).json({ code: 200 }) | ||
}) | ||
.delete(async (req, res) => { | ||
const user = await get.Authorization(req.cookies.token) | ||
|
||
if (!user) return res.status(401).json({ code: 401 }) | ||
|
||
const { token, targetId } = req.body | ||
|
||
if (!token) return res.status(400).json({ code: 400 }) | ||
|
||
const result = global.notification.removeNotification({ userId: user, targetId, token }) | ||
|
||
if (!result) return res.status(400).json({ code: 400 }) | ||
|
||
return res.status(200).json({ code: 200 }) | ||
}) | ||
|
||
export default Notification |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.