-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: set MSW for browser & server #14 #15
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
github actions 확인 요망 |
이전 PR 확인 바랍니다. #13 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
msw 세팅 감사해요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코멘트 참고하셔서 읽으시면 될 거 같습니다.
(더 정확히 파악하고 싶으시면 검색하시면서 읽으시면 될 거 같습니다.)
+공식문서..영어던데 감사합니다ㅎㅎ
self.addEventListener("install", function () { | ||
self.skipWaiting(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
설치
self.addEventListener("activate", function (event) { | ||
event.waitUntil(self.clients.claim()); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
실행
async function resolveMainClient(event) { | ||
const client = await self.clients.get(event.clientId); | ||
|
||
if (client?.frameType === "top-level") { | ||
return client; | ||
} | ||
|
||
const allClients = await self.clients.matchAll({ | ||
type: "window", | ||
}); | ||
|
||
return allClients | ||
.filter((client) => { | ||
return client.visibilityState === "visible"; | ||
}) | ||
.find((client) => { | ||
return activeClientIds.has(client.id); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
탭이나 창 기준으로 클라이언트를 식별하기 때문
-> 개발 중인 탭 찾기
async function respondWithMock(response) { | ||
if (response.status === 0) { | ||
return Response.error(); | ||
} | ||
|
||
const mockedResponse = new Response(response.body, response); | ||
|
||
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { | ||
value: true, | ||
enumerable: true, | ||
}); | ||
|
||
return mockedResponse; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
모의 응답 함수
async function getResponse(event, client, requestId) { | ||
const { request } = event; | ||
|
||
const requestClone = request.clone(); | ||
|
||
function passthrough() { | ||
const headers = Object.fromEntries(requestClone.headers.entries()); | ||
|
||
delete headers["x-msw-intention"]; | ||
|
||
return fetch(requestClone, { headers }); | ||
} | ||
|
||
if (!client) { | ||
return passthrough(); | ||
} | ||
|
||
if (!activeClientIds.has(client.id)) { | ||
return passthrough(); | ||
} | ||
|
||
const mswIntention = request.headers.get("x-msw-intention"); | ||
if (["bypass", "passthrough"].includes(mswIntention)) { | ||
return passthrough(); | ||
} | ||
|
||
const requestBuffer = await request.arrayBuffer(); | ||
const clientMessage = await sendToClient( | ||
client, | ||
{ | ||
type: "REQUEST", | ||
payload: { | ||
id: requestId, | ||
url: request.url, | ||
mode: request.mode, | ||
method: request.method, | ||
headers: Object.fromEntries(request.headers.entries()), | ||
cache: request.cache, | ||
credentials: request.credentials, | ||
destination: request.destination, | ||
integrity: request.integrity, | ||
redirect: request.redirect, | ||
referrer: request.referrer, | ||
referrerPolicy: request.referrerPolicy, | ||
body: requestBuffer, | ||
keepalive: request.keepalive, | ||
}, | ||
}, | ||
[requestBuffer], | ||
); | ||
|
||
switch (clientMessage.type) { | ||
case "MOCK_RESPONSE": { | ||
return respondWithMock(clientMessage.data); | ||
} | ||
|
||
case "MOCK_NOT_FOUND": { | ||
return passthrough(); | ||
} | ||
} | ||
|
||
return passthrough(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-> 모의 응답 있으면 모의 응답해주고 없으면 실제 응답으로 토스
self.addEventListener("message", async function (event) { | ||
const clientId = event.source.id; | ||
|
||
if (!clientId || !self.clients) { | ||
return; | ||
} | ||
|
||
const client = await self.clients.get(clientId); | ||
|
||
if (!client) { | ||
return; | ||
} | ||
|
||
const allClients = await self.clients.matchAll({ | ||
type: "window", | ||
}); | ||
|
||
switch (event.data) { | ||
case "KEEPALIVE_REQUEST": { | ||
sendToClient(client, { | ||
type: "KEEPALIVE_RESPONSE", | ||
}); | ||
break; | ||
} | ||
|
||
case "INTEGRITY_CHECK_REQUEST": { | ||
sendToClient(client, { | ||
type: "INTEGRITY_CHECK_RESPONSE", | ||
payload: INTEGRITY_CHECKSUM, | ||
}); | ||
break; | ||
} | ||
|
||
case "MOCK_ACTIVATE": { | ||
activeClientIds.add(clientId); | ||
|
||
sendToClient(client, { | ||
type: "MOCKING_ENABLED", | ||
payload: true, | ||
}); | ||
break; | ||
} | ||
|
||
case "MOCK_DEACTIVATE": { | ||
activeClientIds.delete(clientId); | ||
break; | ||
} | ||
|
||
case "CLIENT_CLOSED": { | ||
activeClientIds.delete(clientId); | ||
|
||
const remainingClients = allClients.filter((client) => { | ||
return client.id !== clientId; | ||
}); | ||
|
||
if (remainingClients.length === 0) { | ||
self.registration.unregister(); | ||
} | ||
|
||
break; | ||
} | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
메세지에 이벤트 리스너 달고,
클라이언트 ID가 맞는 것만 탐색,
클라이언트 종료 및 시작에 따라 id 제거 및 추가
-> 특정 상태에 따라 모의 기능 활성화 및 종료
self.addEventListener("fetch", function (event) { | ||
const { request } = event; | ||
|
||
if (request.mode === "navigate") { | ||
return; | ||
} | ||
|
||
if (request.cache === "only-if-cached" && request.mode !== "same-origin") { | ||
return; | ||
} | ||
|
||
if (activeClientIds.size === 0) { | ||
return; | ||
} | ||
|
||
const requestId = crypto.randomUUID(); | ||
event.respondWith(handleRequest(event, requestId)); | ||
}); | ||
|
||
async function handleRequest(event, requestId) { | ||
const client = await resolveMainClient(event); | ||
const response = await getResponse(event, client, requestId); | ||
|
||
if (client && activeClientIds.has(client.id)) { | ||
(async function () { | ||
const responseClone = response.clone(); | ||
|
||
sendToClient( | ||
client, | ||
{ | ||
type: "RESPONSE", | ||
payload: { | ||
requestId, | ||
isMockedResponse: IS_MOCKED_RESPONSE in response, | ||
type: responseClone.type, | ||
status: responseClone.status, | ||
statusText: responseClone.statusText, | ||
body: responseClone.body, | ||
headers: Object.fromEntries(responseClone.headers.entries()), | ||
}, | ||
}, | ||
[responseClone.body], | ||
); | ||
})(); | ||
} | ||
|
||
return response; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네트워크 요청을 가로채서 처리(navigate 제외)
요청이 캐싱만 하는 경우? 와 동일출처가 아닌 경우 요청 처리 안함.
요청에 고유 아이디 만들어서 응답 반환
-> 요청 가로채서 응답반환
function sendToClient(client, message, transferrables = []) { | ||
return new Promise((resolve, reject) => { | ||
const channel = new MessageChannel(); | ||
|
||
channel.port1.onmessage = (event) => { | ||
if (event.data && event.data.error) { | ||
return reject(event.data.error); | ||
} | ||
|
||
resolve(event.data); | ||
}; | ||
|
||
client.postMessage( | ||
message, | ||
[channel.port2].concat(transferrables.filter(Boolean)), | ||
); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
메세지 채널 생성
메세지 수신 대기
응답 전송(에러도)
-> 요청 응답 처리 양방향
async function enableMocking() { | ||
if (import.meta.env.MODE !== "development") { | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
별다른 환경변수를 만들지 않아도 된다고 합니다.ㅎㅎ
vite에서 제공해 주는 환경변수라고 합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
환경변수 만든거 없습니다. 기본 vite에서 제공하는 mode에요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
? 뭔가 오해 하신거 같은데ㅎㅎ
다른 분들께 환경변수파일 안만들어도 된다는 의미였어요ㅋㅋㅋㅋ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안그래도 목서버+테스트가 걱정이었는데 종민님 덕에 제대로 공부해보네요! 고생많으셨구 감사히 잘쓰겠습니다~!👍👍
…-server Feat: set MSW for browser & server #14
개요
백엔드 완성 이전 api 명세에 따라 api 요청과 응답 결과를 확인하기 위해 MSW 설정.
.env 설치 / development 모드일 때만 msw 작동하도록 설정.
스크린샷
주요 내용
테스트 결과
closes #14