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
190 changes: 83 additions & 107 deletions public/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
/* tslint:disable */

/**
* Mock Service Worker (0.47.4).
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/

const INTEGRITY_CHECKSUM = 'b3066ef78c2f9090b4ce87e874965995'
const PACKAGE_VERSION = '2.4.5'
const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()

self.addEventListener('install', function () {
Expand Down Expand Up @@ -47,7 +49,10 @@ self.addEventListener('message', async function (event) {
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: INTEGRITY_CHECKSUM,
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}
Expand Down Expand Up @@ -86,12 +91,6 @@ self.addEventListener('message', async function (event) {

self.addEventListener('fetch', function (event) {
const { request } = event
const accept = request.headers.get('accept') || ''

// Bypass server-sent events.
if (accept.includes('text/event-stream')) {
return
}

// Bypass navigation requests.
if (request.mode === 'navigate') {
Expand All @@ -100,10 +99,7 @@ self.addEventListener('fetch', function (event) {

// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (
request.cache === 'only-if-cached' &&
request.mode !== 'same-origin'
) {
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}

Expand All @@ -115,29 +111,8 @@ self.addEventListener('fetch', function (event) {
}

// Generate unique request ID.
const requestId = Math.random().toString(16).slice(2)

event.respondWith(
handleRequest(event, requestId).catch((error) => {
if (error.name === 'NetworkError') {
console.warn(
'[MSW] Successfully emulated a network error for the "%s %s" request.',
request.method,
request.url,
)
return
}

// At this point, any exception indicates an issue with the original request/response.
console.error(
`\
[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
request.method,
request.url,
`${error.name}: ${error.message}`,
)
}),
)
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})

async function handleRequest(event, requestId) {
Expand All @@ -149,23 +124,24 @@ async function handleRequest(event, requestId) {
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const clonedResponse = response.clone()
sendToClient(client, {
type: 'RESPONSE',
payload: {
requestId,
type: clonedResponse.type,
ok: clonedResponse.ok,
status: clonedResponse.status,
statusText: clonedResponse.statusText,
body:
clonedResponse.body === null
? null
: await clonedResponse.text(),
headers: Object.fromEntries(clonedResponse.headers.entries()),
redirected: clonedResponse.redirected,
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],
)
})()
}

Expand All @@ -179,7 +155,7 @@ async function handleRequest(event, requestId) {
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)

if (client.frameType === 'top-level') {
if (client?.frameType === 'top-level') {
return client
}

Expand All @@ -201,20 +177,20 @@ async function resolveMainClient(event) {

async function getResponse(event, client, requestId) {
const { request } = event
const clonedRequest = request.clone()

// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()

function passthrough() {
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const headers = Object.fromEntries(clonedRequest.headers.entries())
const headers = Object.fromEntries(requestClone.headers.entries())

// Remove MSW-specific request headers so the bypassed requests
// comply with the server's CORS preflight check.
// Operate with the headers as an object because request "Headers"
// are immutable.
delete headers['x-msw-bypass']
// Remove internal MSW request header so the passthrough request
// complies with any potential CORS preflight checks on the server.
// Some servers forbid unknown request headers.
delete headers['x-msw-intention']

return fetch(clonedRequest, { headers })
return fetch(requestClone, { headers })
}

// Bypass mocking when the client is not active.
Expand All @@ -230,57 +206,46 @@ async function getResponse(event, client, requestId) {
return passthrough()
}

// Bypass requests with the explicit bypass header.
// Such requests can be issued by "ctx.fetch()".
if (request.headers.get('x-msw-bypass') === 'true') {
return passthrough()
}

// Notify the client that a request has been intercepted.
const clientMessage = await sendToClient(client, {
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
mode: request.mode,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: await request.text(),
bodyUsed: request.bodyUsed,
keepalive: request.keepalive,
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': {
case 'PASSTHROUGH': {
return passthrough()
}

case 'NETWORK_ERROR': {
const { name, message } = clientMessage.data
const networkError = new Error(message)
networkError.name = name

// Rejecting a "respondWith" promise emulates a network error.
throw networkError
}
}

return passthrough()
}

function sendToClient(client, message) {
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()

Expand All @@ -292,17 +257,28 @@ function sendToClient(client, message) {
resolve(event.data)
}

client.postMessage(message, [channel.port2])
client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
})
}

function sleep(timeMs) {
return new Promise((resolve) => {
setTimeout(resolve, timeMs)
async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}

const mockedResponse = new Response(response.body, response)

Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})
}

async function respondWithMock(response) {
await sleep(response.delay)
return new Response(response.body, response)
return mockedResponse
}
57 changes: 39 additions & 18 deletions src/components/Comments/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,21 +221,23 @@ function Comments(props: CommentsProps) {
</IconButton>
</Tooltip>

<ActionDialog
title="Remove comment"
description="This action is irreversible. Are you sure?"
confirmAction={() => handleDelete(comment.id)}
trigger={
<Tooltip title="Remove comment">
<IconButton>
<Icon
name="IoTrashOutline"
className="text-theme-red-900"
/>
</IconButton>
</Tooltip>
}
/>
{comment.by.id === user?.id && (
<ActionDialog
title="Remove comment"
description="This action is irreversible. Are you sure?"
confirmAction={() => handleDelete(comment.id)}
trigger={
<Tooltip title="Remove comment">
<IconButton>
<Icon
name="IoTrashOutline"
className="text-theme-red-900"
/>
</IconButton>
</Tooltip>
}
/>
)}

<Box className="flex items-center gap-2">
{comment.by.id === user?.id ? (
Expand Down Expand Up @@ -301,7 +303,7 @@ function Comments(props: CommentsProps) {
</Avatar>
<ListItemText
primary={reply.comment}
secondary={`by ${reply.by.nickname}, ${formatRelative(new Date(reply.created_at), new Date())}`}
secondary={`by ${reply.by.nickname === user?.nickname ? 'me' : reply.by.nickname}, ${formatRelative(new Date(reply.created_at), new Date())}`}
primaryTypographyProps={{
className:
'dark:text-gray-400 text-gray-600 break-words',
Expand All @@ -321,7 +323,26 @@ function Comments(props: CommentsProps) {
<Icon name="IoChatboxEllipsesOutline" />
</IconButton>
</Tooltip>
{reply.by.id !== user?.id && (
{reply.by.id === user?.id && (
<ActionDialog
title="Remove reply"
description="This action is irreversible. Are you sure?"
confirmAction={() => handleDelete(reply.id)}
trigger={
<Tooltip title="Remove reply">
<IconButton>
<Icon
name="IoTrashOutline"
className="text-theme-red-900"
/>
</IconButton>
</Tooltip>
}
/>
)}
{reply.by.id === user?.id ? (
<Icon name="IoHeartOutline" size={28} />
) : (
<HeartButton
heartable_id={reply.id}
heartable_type="commentables"
Expand All @@ -341,7 +362,7 @@ function Comments(props: CommentsProps) {
)}
<Typography
variant="body2"
className="dark:text-white text-gray-800">
className="dark:text-white text-gray-800 ml-2">
{reply.hearts_count}
</Typography>
</Box>
Expand Down
Loading
Loading