Remove unnecessary dependencies, ensure everything is working (script and noscript).master
@@ -4,11 +4,6 @@ const nextConfig = { | |||
experimental: { | |||
optimizeCss: true, | |||
}, | |||
webpack: (config) => { | |||
config.resolve.fallback = { fs: false }; | |||
return config; | |||
}, | |||
}; | |||
module.exports = nextConfig; |
@@ -73,9 +73,9 @@ const patchNote: NextApiHandler = async (req, res) => { | |||
const updatedNote = { | |||
...note, | |||
title: title ?? note.title, | |||
content: content ?? note.content, | |||
image: image ?? note.image, | |||
title: title || note.title, | |||
content: content || note.content, | |||
image: image ? `data:${image.type};base64,${image.toString('base64')}` : note.image, | |||
}; | |||
data[noteIndex] = updatedNote; | |||
@@ -225,7 +225,7 @@ const createNote = (params: NoteCollectionParams): NextApiHandler => async (req, | |||
id: newId, | |||
title, | |||
content, | |||
image, | |||
image: `data:${image.type};base64,${image.toString('base64')}`, | |||
}); | |||
}; | |||
@@ -68,9 +68,8 @@ | |||
}, | |||
"dependencies": { | |||
"@theoryofnekomata/formxtra": "^1.0.3", | |||
"@web-std/file": "^3.0.3", | |||
"busboy": "^1.6.0", | |||
"nookies": "^2.5.2", | |||
"node-cache": "^5.1.2", | |||
"seroval": "^0.10.2" | |||
}, | |||
"exports": { | |||
@@ -29,6 +29,7 @@ export interface FormProps extends Omit<React.HTMLProps<FormDerivedElement>, 'ac | |||
responseEncType?: string; | |||
serializerOptions?: SerializerOptions; | |||
disableFetch?: boolean; | |||
methodFormKey?: string; | |||
} | |||
export const Form = React.forwardRef<FormDerivedElement, FormProps>(({ | |||
@@ -46,6 +47,7 @@ export const Form = React.forwardRef<FormDerivedElement, FormProps>(({ | |||
responseEncType = ENCTYPE_APPLICATION_JSON, | |||
serializerOptions, | |||
disableFetch = false, | |||
methodFormKey = METHOD_FORM_KEY, | |||
...etcProps | |||
}, forwardedRef) => { | |||
const { handleSubmit } = useFormFetch({ | |||
@@ -83,7 +85,7 @@ export const Form = React.forwardRef<FormDerivedElement, FormProps>(({ | |||
encType={ENCTYPE_MULTIPART_FORM_DATA} | |||
> | |||
{serverMethodOverride && ( | |||
<input type="hidden" name={METHOD_FORM_KEY} value={clientMethod} /> | |||
<input type="hidden" name={methodFormKey} value={clientMethod} /> | |||
)} | |||
{children} | |||
</FormDerivedElementComponent> | |||
@@ -104,4 +106,5 @@ Form.defaultProps = { | |||
refresh: undefined, | |||
responseEncType: ENCTYPE_APPLICATION_JSON, | |||
serializerOptions: undefined, | |||
methodFormKey: METHOD_FORM_KEY, | |||
}; |
@@ -58,7 +58,7 @@ export const useFormFetch = ({ | |||
form: event.currentTarget, | |||
encType, | |||
serializers: encTypeSerializers, | |||
options: { | |||
serializerOptions: { | |||
...serializerOptions, | |||
submitter, | |||
}, | |||
@@ -1,3 +1,8 @@ | |||
export const PREVENT_REDIRECT_FORM_KEY = '__iceform_prevent_redirect' as const; | |||
export const METHOD_FORM_KEY = '__iceform_method' as const; | |||
export const METHODS_WITH_BODY = ['POST', 'PUT', 'PATCH', 'DELETE'] as const; | |||
export const REQUEST_ID_FORM_KEY = '__iceform_request_id' as const; | |||
export const CONTENT_TYPE_HEADER_KEY = 'content-type' as const; | |||
export const LOCATION_HEADER_KEY = 'location' as const; | |||
export const CONTENT_ENCODING_HEADER_KEY = 'content-encoding' as const; | |||
export const DEFAULT_ENCODING = 'utf-8' as const; |
@@ -23,6 +23,9 @@ export interface ActionWrapperOptions { | |||
fn: NextApiHandler, | |||
onAction?: OnActionFunction, | |||
deserializers?: EncTypeDeserializerMap, | |||
methodFormKey?: string, | |||
requestIdFormKey?: string, | |||
preventRedirectFormKey?: string, | |||
/** | |||
* Maps the Location header from the handler response to an accessible URL. | |||
* @param url | |||
@@ -7,7 +7,11 @@ import { | |||
deserializeFormObjectBody, | |||
EncTypeDeserializerMap, | |||
} from '../../utils/serialization'; | |||
import { METHOD_FORM_KEY, PREVENT_REDIRECT_FORM_KEY } from '../../common/constants'; | |||
import { | |||
METHOD_FORM_KEY, | |||
PREVENT_REDIRECT_FORM_KEY, | |||
REQUEST_ID_FORM_KEY, | |||
} from '../../common/constants'; | |||
import { ACTION_STATUS_CODE, DEFAULT_METHOD } from '../constants'; | |||
import { getBody } from '../../utils/request'; | |||
import { | |||
@@ -16,25 +20,21 @@ import { | |||
} from '../../common/enctypes'; | |||
import { ActionWrapperOptions } from './common'; | |||
import { IceformNextServerResponse } from '../response'; | |||
import { | |||
REQUEST_ID_COOKIE_KEY, | |||
CookieManager, | |||
} from '../../utils/cookies'; | |||
import { cacheResponse } from '../cache'; | |||
const getFormObjectMethodAndBody = async ( | |||
req: IncomingMessage, | |||
deserializers: EncTypeDeserializerMap, | |||
methodFormKey = METHOD_FORM_KEY as string, | |||
) => { | |||
const deserialized = await deserializeFormObjectBody({ | |||
req, | |||
deserializers, | |||
}); | |||
const { | |||
[METHOD_FORM_KEY]: method = req.method ?? DEFAULT_METHOD, | |||
[methodFormKey]: method = req.method ?? DEFAULT_METHOD, | |||
...body | |||
} = deserialized as { | |||
[METHOD_FORM_KEY]?: string, | |||
[key: string]: unknown, | |||
}; | |||
@@ -80,7 +80,11 @@ export const getServerSideProps = (options: ActionWrapperOptions): GetServerSide | |||
? getFormObjectMethodAndBody | |||
: getBinaryMethodAndBody | |||
); | |||
const { body, method } = await methodAndBodyFn(ctx.req, deserializers); | |||
const { body, method } = await methodAndBodyFn( | |||
ctx.req, | |||
deserializers, | |||
options.methodFormKey ?? METHOD_FORM_KEY, | |||
); | |||
const req = { | |||
...ctx.req, | |||
body, | |||
@@ -93,9 +97,7 @@ export const getServerSideProps = (options: ActionWrapperOptions): GetServerSide | |||
} as DefaultNextApiRequest; | |||
const res = new IceformNextServerResponse(ctx.req); | |||
await options.fn(req, res); | |||
const requestId = crypto.randomUUID(); | |||
const cookieManager = new CookieManager(ctx); | |||
cookieManager.setCookie(REQUEST_ID_COOKIE_KEY, requestId); | |||
const requestId = crypto.randomBytes(8).toString('base64url'); | |||
await cacheResponse(requestId, res); | |||
if (typeof options.onAction === 'function') { | |||
@@ -112,9 +114,9 @@ export const getServerSideProps = (options: ActionWrapperOptions): GetServerSide | |||
const preventRedirect = ( | |||
typeof req.body === 'object' | |||
&& req.body !== null | |||
&& PREVENT_REDIRECT_FORM_KEY in req.body | |||
&& (options.preventRedirectFormKey ?? PREVENT_REDIRECT_FORM_KEY) in req.body | |||
); | |||
const redirectDestination = ( | |||
const redirectDestinationRaw = ( | |||
res.location | |||
&& typeof options.mapLocationToRedirectDestination === 'function' | |||
&& !preventRedirect | |||
@@ -122,9 +124,12 @@ export const getServerSideProps = (options: ActionWrapperOptions): GetServerSide | |||
? options.mapLocationToRedirectDestination(referer, res.location) | |||
: referer; | |||
const url = new URL(redirectDestinationRaw, 'http://example.com'); | |||
url.searchParams.set(options.requestIdFormKey ?? REQUEST_ID_FORM_KEY, requestId); | |||
return { | |||
redirect: { | |||
destination: redirectDestination, | |||
destination: `/${url.pathname}${url.search}`, | |||
statusCode: ACTION_STATUS_CODE, | |||
}, | |||
props: { | |||
@@ -1,5 +1,6 @@ | |||
import { createWriteStream } from 'fs'; | |||
import { readFile, unlink } from 'fs/promises'; | |||
import NodeCache from 'node-cache'; | |||
import { STATUS_CODES } from 'http'; | |||
import { DEFAULT_RESPONSE_STATUS_CODE } from './constants'; | |||
interface CacheableResponse { | |||
statusCode: number; | |||
@@ -8,41 +9,60 @@ interface CacheableResponse { | |||
data?: unknown; | |||
} | |||
const getFilePathFromRequestId = (requestId: string) => `${requestId}`; | |||
const cache = new NodeCache({ | |||
deleteOnExpire: true, | |||
stdTTL: 60, | |||
}); | |||
export const cacheResponse = async (requestId: string, res: CacheableResponse) => { | |||
const filePath = getFilePathFromRequestId(requestId); | |||
const cacheStream = createWriteStream(filePath, { encoding: 'utf-8' }); | |||
cacheStream.write(`${res.statusCode.toString()} ${res.statusMessage || ''}\n`); | |||
let cacheValue = ''; | |||
cacheValue += `${res.statusCode.toString()} ${res.statusMessage || ''}\n`; | |||
if (res.contentType) { | |||
cacheStream.write(`Content-Type: ${res.contentType}\n`); | |||
cacheValue += `Content-Type: ${res.contentType}\n`; | |||
} | |||
if (res.data) { | |||
cacheStream.write('\n'); | |||
cacheStream.write(res.data as string); | |||
cacheValue += `\n${res.data as string}`; | |||
} | |||
return new Promise((resolve) => { | |||
cacheStream.close(resolve); | |||
return new Promise<void>((resolve) => { | |||
cache.set(requestId, cacheValue); | |||
resolve(); | |||
}); | |||
}; | |||
export const retrieveCache = async (requestId: string) => { | |||
const filePath = getFilePathFromRequestId(requestId); | |||
const requestBuffer = await readFile(filePath, 'utf-8'); | |||
await unlink(filePath); | |||
const [statusLine, ...headersAndBody] = requestBuffer.split('\n'); | |||
const parseCacheValue = (cacheValue: string) => { | |||
const [statusLine, ...headersAndBody] = cacheValue.split('\n'); | |||
const [statusCode, ...statusMessageWords] = statusLine.split(' '); | |||
const statusMessage = statusMessageWords.join(' '); | |||
const bodyStart = headersAndBody.findIndex((line) => line === ''); | |||
const headers = headersAndBody.slice(0, bodyStart); | |||
const body = headersAndBody.slice(bodyStart + 1).join('\n'); | |||
const data = headersAndBody.slice(bodyStart + 1).join('\n'); | |||
const contentTypeHeader = headers.find((header) => header.toLowerCase().startsWith('content-type:')); | |||
const contentType = contentTypeHeader?.split(':')[1].trim(); | |||
return { | |||
statusCode: parseInt(statusCode, 10), | |||
statusMessage, | |||
statusMessage: statusMessage || STATUS_CODES[statusCode], | |||
contentType, | |||
body, | |||
data, | |||
}; | |||
}; | |||
export const retrieveCache = async (requestId: string) => new Promise< | |||
CacheableResponse | |||
>((resolve) => { | |||
const requestBuffer = cache.get<string>(requestId); | |||
if (!requestBuffer) { | |||
resolve({ | |||
statusCode: DEFAULT_RESPONSE_STATUS_CODE, | |||
statusMessage: STATUS_CODES[DEFAULT_RESPONSE_STATUS_CODE], | |||
contentType: undefined, | |||
data: '', | |||
}); | |||
return; | |||
} | |||
cache.del(requestId); | |||
const value = parseCacheValue(requestBuffer); | |||
resolve(value); | |||
}); |
@@ -1,6 +1,3 @@ | |||
export const DEFAULT_METHOD = 'GET' as const; | |||
export const DEFAULT_ENCODING = 'utf-8' as const; | |||
export const ACTION_STATUS_CODE = 307 as const; // temporary redirect | |||
export const DEFAULT_RESPONSE_STATUS_CODE = 200 as const; // ok | |||
export const CONTENT_TYPE_HEADER_KEY = 'content-type' as const; | |||
export const LOCATION_HEADER_KEY = 'location' as const; |
@@ -2,11 +2,10 @@ import { ParsedUrlQuery } from 'querystring'; | |||
import { GetServerSideProps, GetServerSidePropsContext, PreviewData } from 'next'; | |||
import { deserialize } from 'seroval'; | |||
import { NextApiRequest, NextApiResponse } from '../common/types'; | |||
import { DEFAULT_ENCODING, DEFAULT_METHOD } from './constants'; | |||
import { DEFAULT_METHOD } from './constants'; | |||
import { getBody } from '../utils/request'; | |||
import { REQUEST_ID_COOKIE_KEY, CookieManager } from '../utils/cookies'; | |||
import { ENCTYPE_APPLICATION_JSON, ENCTYPE_APPLICATION_OCTET_STREAM } from '../common/enctypes'; | |||
import { METHODS_WITH_BODY } from '../common/constants'; | |||
import { METHODS_WITH_BODY, REQUEST_ID_FORM_KEY, DEFAULT_ENCODING } from '../common/constants'; | |||
import { retrieveCache } from './cache'; | |||
export type DestinationGetServerSideProps< | |||
@@ -21,6 +20,7 @@ export type DestinationGetServerSideProps< | |||
export interface DestinationWrapperOptions { | |||
fn?: DestinationGetServerSideProps; | |||
requestIdKey?: string; | |||
} | |||
export const getServerSideProps = ( | |||
@@ -41,26 +41,31 @@ export const getServerSideProps = ( | |||
} | |||
const res: NextApiResponse = {}; | |||
const cookieManager = new CookieManager(ctx); | |||
const resRequestId = cookieManager.getCookie(REQUEST_ID_COOKIE_KEY); | |||
cookieManager.unsetCookie(REQUEST_ID_COOKIE_KEY); | |||
const resRequestId = ctx.query[ | |||
options.requestIdKey ?? REQUEST_ID_FORM_KEY | |||
] as string | undefined; | |||
if (resRequestId) { | |||
const { | |||
statusCode, | |||
statusMessage, | |||
contentType, | |||
body: resBody, | |||
data: resBody, | |||
} = await retrieveCache(resRequestId); | |||
ctx.res.statusCode = statusCode; | |||
ctx.res.statusMessage = statusMessage; | |||
if (contentType === ENCTYPE_APPLICATION_JSON) { | |||
res.body = deserialize(resBody); | |||
} else if (contentType === ENCTYPE_APPLICATION_OCTET_STREAM) { | |||
res.body = deserialize(resBody); | |||
if (typeof statusMessage !== 'undefined') { | |||
ctx.res.statusMessage = statusMessage; | |||
} | |||
if (resBody) { | |||
if (contentType === ENCTYPE_APPLICATION_JSON) { | |||
res.body = deserialize(resBody as string); | |||
} else if (contentType === ENCTYPE_APPLICATION_OCTET_STREAM) { | |||
res.body = deserialize(resBody as string); | |||
} else { | |||
const c = console; | |||
c.warn('Could not parse response body, returning nothing'); | |||
res.body = null; | |||
} | |||
} else { | |||
const c = console; | |||
c.warn('Could not parse response body, returning nothing'); | |||
res.body = null; | |||
} | |||
} | |||
@@ -1,11 +1,12 @@ | |||
import { ServerResponse } from 'http'; | |||
import { NextApiResponse as DefaultNextApiResponse } from 'next/dist/shared/lib/utils'; | |||
import { serialize } from 'seroval'; | |||
import { ENCTYPE_APPLICATION_JSON, ENCTYPE_APPLICATION_OCTET_STREAM } from '../common/enctypes'; | |||
import { | |||
ACTION_STATUS_CODE, CONTENT_TYPE_HEADER_KEY, LOCATION_HEADER_KEY, DEFAULT_RESPONSE_STATUS_CODE, | |||
ACTION_STATUS_CODE, | |||
DEFAULT_RESPONSE_STATUS_CODE, | |||
} from './constants'; | |||
import { CONTENT_TYPE_HEADER_KEY, LOCATION_HEADER_KEY } from '../common/constants'; | |||
// for client-side | |||
class DummyServerResponse {} | |||
@@ -1,56 +0,0 @@ | |||
import { IncomingMessage, ServerResponse } from 'http'; | |||
import * as nookies from 'nookies'; | |||
import * as crypto from 'crypto'; | |||
const COMMON_COOKIE_CONFIG = { | |||
path: '/', | |||
httpOnly: true, | |||
}; | |||
const COMMON_SET_COOKIE_CONFIG = { | |||
...COMMON_COOKIE_CONFIG, | |||
maxAge: 30 * 24 * 60 * 60, | |||
}; | |||
const cookieKeys: Record<string, string> = {}; | |||
export const REQUEST_ID_COOKIE_KEY = 'b' as const; | |||
export class CookieManager { | |||
private readonly ctx: { req: IncomingMessage, res: ServerResponse<IncomingMessage> }; | |||
constructor(ctx: { req: IncomingMessage, res: ServerResponse<IncomingMessage> }) { | |||
// noop | |||
this.ctx = ctx; | |||
} | |||
private static generateCookieKey(key: string) { | |||
const random = crypto.randomBytes(16).toString('hex'); | |||
return `if${key}${random}`; | |||
} | |||
setCookie(key: string, value: string) { | |||
// cleanup previous cookie | |||
this.unsetCookie(key); | |||
cookieKeys[key] = CookieManager.generateCookieKey(key); | |||
nookies.setCookie( | |||
this.ctx, | |||
cookieKeys[key], | |||
value, | |||
COMMON_SET_COOKIE_CONFIG, | |||
); | |||
} | |||
unsetCookie(key: string) { | |||
nookies.destroyCookie(this.ctx, cookieKeys[key], COMMON_COOKIE_CONFIG); | |||
} | |||
hasCookie(key: string) { | |||
const cookies = nookies.parseCookies(this.ctx); | |||
return cookieKeys[key] in cookies; | |||
} | |||
getCookie(key: string) { | |||
const cookies = nookies.parseCookies(this.ctx); | |||
return cookies[cookieKeys[key]]; | |||
} | |||
} |
@@ -37,6 +37,11 @@ export const parseMultipartFormData = async ( | |||
}); | |||
file.on('close', () => { | |||
// filename can be undefined somehow... | |||
if (!filename && buffer.length <= 0) { | |||
return; | |||
} | |||
const bufferMut = buffer as unknown as Record<string, unknown>; | |||
bufferMut.name = filename; | |||
bufferMut.type = mimeType; | |||
@@ -6,8 +6,13 @@ import { | |||
ENCTYPE_X_WWW_FORM_URLENCODED, | |||
} from '../common/enctypes'; | |||
import { getBody, parseMultipartFormData } from './request'; | |||
import { METHOD_FORM_KEY, PREVENT_REDIRECT_FORM_KEY } from '../common/constants'; | |||
import { DEFAULT_ENCODING } from '../server/constants'; | |||
import { | |||
METHOD_FORM_KEY, | |||
PREVENT_REDIRECT_FORM_KEY, | |||
CONTENT_ENCODING_HEADER_KEY, | |||
CONTENT_TYPE_HEADER_KEY, | |||
DEFAULT_ENCODING, | |||
} from '../common/constants'; | |||
export type EncTypeSerializer = (data: unknown) => string; | |||
@@ -19,7 +24,9 @@ export interface SerializeBodyParams { | |||
form: HTMLFormElement, | |||
encType: string, | |||
serializers?: EncTypeSerializerMap, | |||
options?: SerializerOptions, | |||
serializerOptions?: SerializerOptions, | |||
methodFormKey?: string, | |||
preventRedirectFormKey?: string, | |||
} | |||
export const DEFAULT_ENCTYPE_SERIALIZERS: EncTypeSerializerMap = { | |||
@@ -31,13 +38,15 @@ export const serializeBody = (params: SerializeBodyParams) => { | |||
form, | |||
encType, | |||
serializers = DEFAULT_ENCTYPE_SERIALIZERS, | |||
options, | |||
serializerOptions, | |||
methodFormKey = METHOD_FORM_KEY, | |||
preventRedirectFormKey = PREVENT_REDIRECT_FORM_KEY, | |||
} = params; | |||
if (encType === ENCTYPE_X_WWW_FORM_URLENCODED) { | |||
const searchParams = new URLSearchParams(form); | |||
searchParams.delete(METHOD_FORM_KEY); | |||
searchParams.delete(PREVENT_REDIRECT_FORM_KEY); | |||
searchParams.delete(methodFormKey); | |||
searchParams.delete(preventRedirectFormKey); | |||
return searchParams; | |||
} | |||
@@ -48,23 +57,25 @@ export const serializeBody = (params: SerializeBodyParams) => { | |||
entries: () => IterableIterator<[string, unknown]>; | |||
}; | |||
}; | |||
const formData = new FormDataUnknown(form, options?.submitter); | |||
const formData = new FormDataUnknown(form, serializerOptions?.submitter); | |||
const emptyFiles = Array.from(formData.entries()) | |||
.filter(([, value]) => ( | |||
value instanceof File && value.size === 0 && value.name === '' | |||
)); | |||
emptyFiles.forEach(([key]) => formData.delete(key)); | |||
formData.delete(METHOD_FORM_KEY); | |||
formData.delete(PREVENT_REDIRECT_FORM_KEY); | |||
formData.delete(methodFormKey); | |||
formData.delete(preventRedirectFormKey); | |||
return formData; | |||
} | |||
if (typeof serializers[encType] === 'function') { | |||
const { | |||
[METHOD_FORM_KEY]: _method, | |||
[PREVENT_REDIRECT_FORM_KEY]: _preventRedirect, | |||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | |||
[methodFormKey]: _method, | |||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | |||
[preventRedirectFormKey]: _preventRedirect, | |||
...formValues | |||
} = getFormValues(form, options); | |||
} = getFormValues(form, serializerOptions); | |||
return serializers[encType](formValues); | |||
} | |||
@@ -90,7 +101,7 @@ export const DEFAULT_ENCTYPE_DESERIALIZERS: EncTypeDeserializerMap = { | |||
export const deserializeFormObjectBody = async (params: DeserializeBodyParams) => { | |||
const { req, deserializers = DEFAULT_ENCTYPE_DESERIALIZERS } = params; | |||
const contentType = req.headers['content-type'] ?? ENCTYPE_APPLICATION_OCTET_STREAM; | |||
const contentType = req.headers[CONTENT_TYPE_HEADER_KEY] ?? ENCTYPE_APPLICATION_OCTET_STREAM; | |||
if (contentType?.startsWith(`${ENCTYPE_MULTIPART_FORM_DATA};`)) { | |||
return parseMultipartFormData(req); | |||
@@ -98,7 +109,7 @@ export const deserializeFormObjectBody = async (params: DeserializeBodyParams) = | |||
const bodyRaw = await getBody(req); | |||
const encoding = ( | |||
req.headers['content-encoding'] ?? DEFAULT_ENCODING | |||
req.headers[CONTENT_ENCODING_HEADER_KEY] ?? DEFAULT_ENCODING | |||
) as BufferEncoding; | |||
const { [contentType]: theDeserializer } = deserializers; | |||
@@ -11,15 +11,12 @@ importers: | |||
'@theoryofnekomata/formxtra': | |||
specifier: ^1.0.3 | |||
version: 1.0.3 | |||
'@web-std/file': | |||
specifier: ^3.0.3 | |||
version: 3.0.3 | |||
busboy: | |||
specifier: ^1.6.0 | |||
version: 1.6.0 | |||
nookies: | |||
specifier: ^2.5.2 | |||
version: 2.5.2 | |||
node-cache: | |||
specifier: ^5.1.2 | |||
version: 5.1.2 | |||
seroval: | |||
specifier: ^0.10.2 | |||
version: 0.10.2 | |||
@@ -1515,31 +1512,6 @@ packages: | |||
pretty-format: 29.7.0 | |||
dev: true | |||
/@web-std/blob@3.0.5: | |||
resolution: {integrity: sha512-Lm03qr0eT3PoLBuhkvFBLf0EFkAsNz/G/AYCzpOdi483aFaVX86b4iQs0OHhzHJfN5C15q17UtDbyABjlzM96A==} | |||
dependencies: | |||
'@web-std/stream': 1.0.0 | |||
web-encoding: 1.1.5 | |||
dev: false | |||
/@web-std/file@3.0.3: | |||
resolution: {integrity: sha512-X7YYyvEERBbaDfJeC9lBKC5Q5lIEWYCP1SNftJNwNH/VbFhdHm+3neKOQP+kWEYJmosbDFq+NEUG7+XIvet/Jw==} | |||
dependencies: | |||
'@web-std/blob': 3.0.5 | |||
dev: false | |||
/@web-std/stream@1.0.0: | |||
resolution: {integrity: sha512-jyIbdVl+0ZJyKGTV0Ohb9E6UnxP+t7ZzX4Do3AHjZKxUXKMs9EmqnBDQgHF7bEw0EzbQygOjtt/7gvtmi//iCQ==} | |||
dependencies: | |||
web-streams-polyfill: 3.2.1 | |||
dev: false | |||
/@zxing/text-encoding@0.9.0: | |||
resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} | |||
requiresBuild: true | |||
dev: false | |||
optional: true | |||
/abab@2.0.6: | |||
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} | |||
dev: true | |||
@@ -1987,6 +1959,11 @@ packages: | |||
engines: {node: '>=0.8'} | |||
dev: true | |||
/clone@2.1.2: | |||
resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} | |||
engines: {node: '>=0.8'} | |||
dev: false | |||
/color-convert@1.9.3: | |||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} | |||
dependencies: | |||
@@ -3578,6 +3555,7 @@ packages: | |||
dependencies: | |||
call-bind: 1.0.2 | |||
has-tostringtag: 1.0.0 | |||
dev: true | |||
/is-array-buffer@3.0.2: | |||
resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} | |||
@@ -4224,6 +4202,13 @@ packages: | |||
- '@babel/core' | |||
- babel-plugin-macros | |||
/node-cache@5.1.2: | |||
resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} | |||
engines: {node: '>= 8.0.0'} | |||
dependencies: | |||
clone: 2.1.2 | |||
dev: false | |||
/node-releases@2.0.13: | |||
resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} | |||
@@ -5457,16 +5442,6 @@ packages: | |||
/util-deprecate@1.0.2: | |||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} | |||
/util@0.12.5: | |||
resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} | |||
dependencies: | |||
inherits: 2.0.4 | |||
is-arguments: 1.1.1 | |||
is-generator-function: 1.0.10 | |||
is-typed-array: 1.1.12 | |||
which-typed-array: 1.1.11 | |||
dev: false | |||
/utils-merge@1.0.1: | |||
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} | |||
engines: {node: '>= 0.4.0'} | |||
@@ -5648,19 +5623,6 @@ packages: | |||
defaults: 1.0.4 | |||
dev: true | |||
/web-encoding@1.1.5: | |||
resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==} | |||
dependencies: | |||
util: 0.12.5 | |||
optionalDependencies: | |||
'@zxing/text-encoding': 0.9.0 | |||
dev: false | |||
/web-streams-polyfill@3.2.1: | |||
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} | |||
engines: {node: '>= 8'} | |||
dev: false | |||
/webidl-conversions@7.0.0: | |||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} | |||
engines: {node: '>=12'} | |||