diff --git a/packages/remix/package.json b/packages/remix/package.json index b6c1afcd3c4f..dc3794f77495 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -34,12 +34,13 @@ "@sentry/types": "7.73.0", "@sentry/utils": "7.73.0", "glob": "^10.3.4", + "is-ip": "^3.1.0", "tslib": "^2.4.1 || ^1.9.3", "yargs": "^17.6.0" }, "devDependencies": { - "@remix-run/node": "^1.4.3", - "@remix-run/react": "^1.4.3", + "@remix-run/node": "^1.19.3", + "@remix-run/react": "^1.19.3", "@types/express": "^4.17.14" }, "peerDependencies": { @@ -86,6 +87,8 @@ }, "sideEffects": [ "./esm/index.server.js", - "./src/index.server.ts" + "./src/index.server.ts", + "./src/index.worker.ts", + "./esm/index.worker.js" ] } diff --git a/packages/remix/rollup.npm.config.js b/packages/remix/rollup.npm.config.js index dc51f24c7bb2..f8ed9410309b 100644 --- a/packages/remix/rollup.npm.config.js +++ b/packages/remix/rollup.npm.config.js @@ -2,7 +2,7 @@ import { makeBaseNPMConfig, makeNPMConfigVariants } from '../../rollup/index.js' export default makeNPMConfigVariants( makeBaseNPMConfig({ - entrypoints: ['src/index.server.ts', 'src/index.client.tsx'], + entrypoints: ['src/index.server.ts', 'src/index.client.tsx', 'src/index.worker.ts'], packageSpecificConfig: { external: ['react-router', 'react-router-dom'], output: { diff --git a/packages/remix/src/client/errors.tsx b/packages/remix/src/client/errors.tsx index 6732ed5cdc91..8182666612a2 100644 --- a/packages/remix/src/client/errors.tsx +++ b/packages/remix/src/client/errors.tsx @@ -1,5 +1,5 @@ import { captureException, withScope } from '@sentry/core'; -import { addExceptionMechanism, isNodeEnv, isString } from '@sentry/utils'; +import { addExceptionMechanism, isBrowser, isString } from '@sentry/utils'; import { isRouteErrorResponse } from '../utils/vendor/response'; @@ -11,7 +11,7 @@ import { isRouteErrorResponse } from '../utils/vendor/response'; */ export function captureRemixErrorBoundaryError(error: unknown): string | undefined { let eventId: string | undefined; - const isClientSideRuntimeError = !isNodeEnv() && error instanceof Error; + const isClientSideRuntimeError = isBrowser() && error instanceof Error; const isRemixErrorResponse = isRouteErrorResponse(error); // Server-side errors apart from `ErrorResponse`s also appear here without their stacktraces. // So, we only capture: diff --git a/packages/remix/src/client/performance.tsx b/packages/remix/src/client/performance.tsx index 97c455a80a94..9895f1bd8e17 100644 --- a/packages/remix/src/client/performance.tsx +++ b/packages/remix/src/client/performance.tsx @@ -1,7 +1,7 @@ import type { ErrorBoundaryProps } from '@sentry/react'; import { WINDOW, withErrorBoundary } from '@sentry/react'; import type { Transaction, TransactionContext } from '@sentry/types'; -import { isNodeEnv, logger } from '@sentry/utils'; +import { isBrowser, logger } from '@sentry/utils'; import * as React from 'react'; import { getFutureFlagsBrowser, readRemixVersionFromLoader } from '../utils/futureFlags'; @@ -109,7 +109,7 @@ export function withSentry

, R extends React.Co // Early return when any of the required functions is not available. if (!_useEffect || !_useLocation || !_useMatches || !_customStartTransaction) { __DEBUG_BUILD__ && - !isNodeEnv() && + isBrowser() && logger.warn('Remix SDK was unable to wrap your root because of one or more missing parameters.'); // @ts-expect-error Setting more specific React Component typing for `R` generic above diff --git a/packages/remix/src/index.client.tsx b/packages/remix/src/index.client.tsx index 64951a3f10cd..a817450c2615 100644 --- a/packages/remix/src/index.client.tsx +++ b/packages/remix/src/index.client.tsx @@ -1,11 +1,28 @@ /* eslint-disable import/export */ -import { configureScope, init as reactInit } from '@sentry/react'; +import { init as reactInit } from '@sentry/react'; import { buildMetadata } from './utils/metadata'; import type { RemixOptions } from './utils/remixOptions'; export { remixRouterInstrumentation, withSentry } from './client/performance'; export { captureRemixErrorBoundaryError } from './client/errors'; export * from '@sentry/react'; +import type { ServerRuntimeClientOptions } from '@sentry/core'; +import { + configureScope, + getCurrentHub, + getIntegrationsToSetup, + initAndBind, + ServerRuntimeClient, + startTransaction, +} from '@sentry/core'; +import { createStackParser, logger, nodeStackLineParser, stackParserFromStackParserOptions } from '@sentry/utils'; + +import { makeEdgeTransport } from './worker/transport'; + +export { captureRemixServerException } from './utils/instrumentServer'; +export { ErrorBoundary, withErrorBoundary } from '@sentry/react'; +// export { wrapExpressCreateRequestHandler } from './utils/serverAdapters/express'; +export { wrapWorkerCreateRequestHandler } from './utils/serverAdapters/worker'; export function init(options: RemixOptions): void { buildMetadata(options, ['remix', 'react']); @@ -17,3 +34,41 @@ export function init(options: RemixOptions): void { scope.setTag('runtime', 'browser'); }); } + +const nodeStackParser = createStackParser(nodeStackLineParser()); + +function sdkAlreadyInitialized(): boolean { + const hub = getCurrentHub(); + return !!hub.getClient(); +} + +/** Initializes Sentry Remix SDK on Node. */ +export function workerInit(options: RemixOptions): void { + buildMetadata(options, ['remix', 'node']); + + if (sdkAlreadyInitialized()) { + __DEBUG_BUILD__ && logger.log('SDK already initialized'); + + return; + } + + const clientOptions: ServerRuntimeClientOptions = { + ...options, + stackParser: stackParserFromStackParserOptions(options.stackParser || nodeStackParser), + integrations: getIntegrationsToSetup(options), + transport: options.transport || makeEdgeTransport, + }; + + initAndBind(ServerRuntimeClient, clientOptions); + + configureScope(scope => { + scope.setTag('runtime', 'worker'); + }); + + const transaction = startTransaction({ + name: 'remix-main', + op: 'init', + }); + + transaction.finish(); +} diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 53757b4a89f4..43b2ca6b6daa 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -60,6 +60,7 @@ export { ErrorBoundary, withErrorBoundary } from '@sentry/react'; export { remixRouterInstrumentation, withSentry } from './client/performance'; export { captureRemixErrorBoundaryError } from './client/errors'; export { wrapExpressCreateRequestHandler } from './utils/serverAdapters/express'; +export { wrapWorkerCreateRequestHandler } from './utils/serverAdapters/worker'; function sdkAlreadyInitialized(): boolean { const hub = getCurrentHub(); diff --git a/packages/remix/src/index.worker.ts b/packages/remix/src/index.worker.ts new file mode 100644 index 000000000000..619afd2afe14 --- /dev/null +++ b/packages/remix/src/index.worker.ts @@ -0,0 +1,46 @@ +import type { ServerRuntimeClientOptions } from '@sentry/core'; +import { configureScope, getCurrentHub, getIntegrationsToSetup, initAndBind, ServerRuntimeClient } from '@sentry/core'; +import { createStackParser, logger, nodeStackLineParser, stackParserFromStackParserOptions } from '@sentry/utils'; + +import { instrumentServer } from './utils/instrumentServer'; +import { buildMetadata } from './utils/metadata'; +import type { RemixOptions } from './utils/remixOptions'; +import { makeEdgeTransport } from './worker/transport'; + +export { captureRemixServerException } from './utils/instrumentServer'; +export { ErrorBoundary, withErrorBoundary } from '@sentry/react'; +export { remixRouterInstrumentation, withSentry } from './client/performance'; +export { captureRemixErrorBoundaryError } from './client/errors'; +export { wrapExpressCreateRequestHandler } from './utils/serverAdapters/express'; + +const nodeStackParser = createStackParser(nodeStackLineParser()); + +function sdkAlreadyInitialized(): boolean { + const hub = getCurrentHub(); + return !!hub.getClient(); +} + +/** Initializes Sentry Remix SDK on Node. */ +export function init(options: RemixOptions): void { + buildMetadata(options, ['remix', 'node']); + + if (sdkAlreadyInitialized()) { + __DEBUG_BUILD__ && logger.log('SDK already initialized'); + + return; + } + + const clientOptions: ServerRuntimeClientOptions = { + ...options, + stackParser: stackParserFromStackParserOptions(options.stackParser || nodeStackParser), + integrations: getIntegrationsToSetup(options), + transport: options.transport || makeEdgeTransport, + }; + + initAndBind(ServerRuntimeClient, clientOptions); + instrumentServer(); + + configureScope(scope => { + scope.setTag('runtime', 'worker'); + }); +} diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index a80e795fb9a6..590ef96ca932 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -1,13 +1,18 @@ /* eslint-disable max-lines */ -import { getActiveTransaction, hasTracingEnabled, runWithAsyncContext } from '@sentry/core'; -import type { Hub } from '@sentry/node'; -import { captureException, getCurrentHub } from '@sentry/node'; +import type { Hub } from '@sentry/core'; +import { + captureException, + getActiveTransaction, + getCurrentHub, + hasTracingEnabled, + runWithAsyncContext, +} from '@sentry/core'; import type { Transaction, TransactionSource, WrappedFunction } from '@sentry/types'; import { addExceptionMechanism, dynamicSamplingContextToSentryBaggageHeader, fill, - isNodeEnv, + isBrowser, loadModule, logger, tracingContextFromHeaders, @@ -236,7 +241,7 @@ function getTraceAndBaggage(): { sentryTrace?: string; sentryBaggage?: string } const transaction = getActiveTransaction(); const currentScope = getCurrentHub().getScope(); - if (isNodeEnv() && hasTracingEnabled()) { + if (!isBrowser() && hasTracingEnabled()) { const span = currentScope.getSpan(); if (span && transaction) { @@ -252,18 +257,17 @@ function getTraceAndBaggage(): { sentryTrace?: string; sentryBaggage?: string } return {}; } -function makeWrappedRootLoader(remixVersion: number) { +function makeWrappedRootLoader() { return function (origLoader: DataFunction): DataFunction { return async function (this: unknown, args: DataFunctionArgs): Promise { const res = await origLoader.call(this, args); const traceAndBaggage = getTraceAndBaggage(); if (isDeferredData(res)) { - return { - ...res.data, - ...traceAndBaggage, - remixVersion, - }; + res.data['sentryTrace'] = traceAndBaggage.sentryTrace; + res.data['sentryBaggage'] = traceAndBaggage.sentryBaggage; + + return res; } if (isResponse(res)) { @@ -279,7 +283,7 @@ function makeWrappedRootLoader(remixVersion: number) { if (typeof data === 'object') { return json( - { ...data, ...traceAndBaggage, remixVersion }, + { ...data, ...traceAndBaggage }, { headers: res.headers, statusText: res.statusText, status: res.status }, ); } else { @@ -290,7 +294,7 @@ function makeWrappedRootLoader(remixVersion: number) { } } - return { ...res, ...traceAndBaggage, remixVersion }; + return { ...res, ...traceAndBaggage }; }; }; } @@ -462,7 +466,7 @@ export function instrumentBuild(build: ServerBuild): ServerBuild { } // We want to wrap the root loader regardless of whether it's already wrapped before. - fill(wrappedRoute.module, 'loader', makeWrappedRootLoader(remixVersion)); + fill(wrappedRoute.module, 'loader', makeWrappedRootLoader()); } routes[id] = wrappedRoute; diff --git a/packages/remix/src/utils/serverAdapters/worker.ts b/packages/remix/src/utils/serverAdapters/worker.ts new file mode 100644 index 000000000000..88832183f36e --- /dev/null +++ b/packages/remix/src/utils/serverAdapters/worker.ts @@ -0,0 +1,83 @@ +import { getCurrentHub, hasTracingEnabled } from '@sentry/core'; +import { isString, logger } from '@sentry/utils'; + +import { + createRoutes, + getTransactionName, + instrumentBuild, + isRequestHandlerWrapped, + startRequestHandlerTransaction, +} from '../instrumentServer'; +import type { ReactRouterDomPkg, ServerBuild } from '../vendor/types'; + +type WorkerRequestHandler = (request: Request) => Promise; +export type WorkerCreateRequestHandler = (this: unknown, options: any) => WorkerRequestHandler; +type WorkerRequestHandlerOptions = { + build: ServerBuild; + mode?: string; + poweredByHeader?: boolean; + getLoadContext?: (request: Request) => Promise | unknown; +}; + +let pkg: ReactRouterDomPkg; + +function wrapWorkerRequestHandler(origRequestHandler: WorkerRequestHandler, build: ServerBuild): WorkerRequestHandler { + const routes = createRoutes(build.routes); + + // If the core request handler is already wrapped, don't wrap Express handler which uses it. + if (isRequestHandlerWrapped) { + return origRequestHandler; + } + + return async function (this: unknown, request: Request): Promise { + if (!pkg) { + try { + pkg = await import('react-router-dom'); + } finally { + if (!pkg) { + __DEBUG_BUILD__ && logger.error('Could not find `react-router-dom` package.'); + } + } + } + + const hub = getCurrentHub(); + const options = hub.getClient()?.getOptions(); + const scope = hub.getScope(); + + scope.setSDKProcessingMetadata({ request }); + + if (!options || !hasTracingEnabled(options) || !request.url || !request.method) { + return origRequestHandler.call(this, request); + } + + const url = new URL(request.url); + const [name, source] = getTransactionName(routes, url, pkg); + startRequestHandlerTransaction(hub, name, source, { + headers: { + 'sentry-trace': + (request.headers && isString(request.headers.get('sentry-trace')) && request.headers.get('sentry-trace')) || + '', + baggage: (request.headers && isString(request.headers.get('baggage')) && request.headers.get('baggage')) || '', + }, + method: request.method, + }); + + return origRequestHandler.call(this, request); + }; +} + +/** + * Instruments `createRequestHandler` from `@remix-run/express` + */ +export function wrapWorkerCreateRequestHandler( + origCreateRequestHandler: WorkerCreateRequestHandler, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): (options: any) => WorkerRequestHandler { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function (this: unknown, options: any): WorkerRequestHandler { + const newBuild = instrumentBuild((options as WorkerRequestHandlerOptions).build); + const requestHandler = origCreateRequestHandler.call(this, { ...options, build: newBuild }); + + return wrapWorkerRequestHandler(requestHandler, newBuild); + }; +} diff --git a/packages/remix/src/utils/vendor/getIpAddress.ts b/packages/remix/src/utils/vendor/getIpAddress.ts index d63e31779aac..52fef0db1f7f 100644 --- a/packages/remix/src/utils/vendor/getIpAddress.ts +++ b/packages/remix/src/utils/vendor/getIpAddress.ts @@ -23,8 +23,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import { isIP } from 'net'; - +import isIP from 'is-ip'; /** * Get the IP address of the client sending a request. * diff --git a/packages/remix/src/utils/vendor/types.ts b/packages/remix/src/utils/vendor/types.ts index faaa7e5f6f60..da5c2a19b595 100644 --- a/packages/remix/src/utils/vendor/types.ts +++ b/packages/remix/src/utils/vendor/types.ts @@ -202,7 +202,7 @@ export interface DataFunction { } export interface ReactRouterDomPkg { - matchRoutes: (routes: ServerRoute[], pathname: string) => RouteMatch[] | null; + matchRoutes: (routes: any[], pathname: string) => RouteMatch[] | null; } // Taken from Remix Implementation diff --git a/packages/remix/src/worker/transport.ts b/packages/remix/src/worker/transport.ts new file mode 100644 index 000000000000..a479425f96e6 --- /dev/null +++ b/packages/remix/src/worker/transport.ts @@ -0,0 +1,103 @@ +import { createTransport } from '@sentry/core'; +import type { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types'; +import { SentryError } from '@sentry/utils'; + +export interface VercelEdgeTransportOptions extends BaseTransportOptions { + /** Fetch API init parameters. */ + fetchOptions?: RequestInit; + /** Custom headers for the transport. */ + headers?: { [key: string]: string }; +} + +const DEFAULT_TRANSPORT_BUFFER_SIZE = 30; + +/** + * This is a modified promise buffer that collects tasks until drain is called. + * We need this in the edge runtime because edge function invocations may not share I/O objects, like fetch requests + * and responses, and the normal PromiseBuffer inherently buffers stuff inbetween incoming requests. + * + * A limitation we need to be aware of is that DEFAULT_TRANSPORT_BUFFER_SIZE is the maximum amount of payloads the + * SDK can send for a given edge function invocation. + */ +export class IsolatedPromiseBuffer { + // We just have this field because the promise buffer interface requires it. + // If we ever remove it from the interface we should also remove it here. + public $: Array>; + + private _taskProducers: (() => PromiseLike)[]; + + private readonly _bufferSize: number; + + public constructor(_bufferSize = DEFAULT_TRANSPORT_BUFFER_SIZE) { + this.$ = []; + this._taskProducers = []; + this._bufferSize = _bufferSize; + } + + /** + * @inheritdoc + */ + public add(taskProducer: () => PromiseLike): PromiseLike { + if (this._taskProducers.length >= this._bufferSize) { + return Promise.reject(new SentryError('Not adding Promise because buffer limit was reached.')); + } + + this._taskProducers.push(taskProducer); + return Promise.resolve(); + } + + /** + * @inheritdoc + */ + public drain(timeout?: number): PromiseLike { + const oldTaskProducers = [...this._taskProducers]; + this._taskProducers = []; + + return new Promise(resolve => { + const timer = setTimeout(() => { + if (timeout && timeout > 0) { + resolve(false); + } + }, timeout); + + void Promise.all( + oldTaskProducers.map(taskProducer => + taskProducer().then(null, () => { + // catch all failed requests + }), + ), + ).then(() => { + // resolve to true if all fetch requests settled + clearTimeout(timer); + resolve(true); + }); + }); + } +} + +/** + * Creates a Transport that uses the Edge Runtimes native fetch API to send events to Sentry. + */ +export function makeEdgeTransport(options: VercelEdgeTransportOptions): Transport { + function makeRequest(request: TransportRequest): PromiseLike { + const requestOptions: RequestInit = { + body: request.body, + method: 'POST', + referrerPolicy: 'origin', + headers: options.headers, + ...options.fetchOptions, + }; + + return fetch(options.url, requestOptions).then(response => { + return { + statusCode: response.status, + headers: { + 'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'), + 'retry-after': response.headers.get('Retry-After'), + }, + }; + }); + } + + return createTransport(options, makeRequest, new IsolatedPromiseBuffer(options.bufferSize)); +} diff --git a/packages/replay/jest.setup.ts b/packages/replay/jest.setup.ts index b44298a751e1..1b6424bb7c93 100644 --- a/packages/replay/jest.setup.ts +++ b/packages/replay/jest.setup.ts @@ -10,8 +10,9 @@ import type { ReplayContainer, Session } from './src/types'; type MockTransport = jest.MockedFunction; -jest.mock('./src/util/isBrowser', () => { +jest.mock('@sentry/utils', () => { return { + ...jest.requireActual('@sentry/utils'), isBrowser: () => true, }; }); diff --git a/packages/replay/src/integration.ts b/packages/replay/src/integration.ts index 753fd62d5660..6522e4dd1c6f 100644 --- a/packages/replay/src/integration.ts +++ b/packages/replay/src/integration.ts @@ -1,6 +1,6 @@ import { getCurrentHub } from '@sentry/core'; import type { BrowserClientReplayOptions, Integration } from '@sentry/types'; -import { dropUndefinedKeys } from '@sentry/utils'; +import { dropUndefinedKeys, isBrowser } from '@sentry/utils'; import { DEFAULT_FLUSH_MAX_DELAY, @@ -12,7 +12,6 @@ import { import { ReplayContainer } from './replay'; import type { RecordingOptions, ReplayConfiguration, ReplayPluginOptions, SendBufferedReplayOptions } from './types'; import { getPrivacyOptions } from './util/getPrivacyOptions'; -import { isBrowser } from './util/isBrowser'; import { maskAttribute } from './util/maskAttribute'; const MEDIA_SELECTORS = diff --git a/packages/replay/src/util/isBrowser.ts b/packages/replay/src/util/isBrowser.ts deleted file mode 100644 index 6a64317ba3fa..000000000000 --- a/packages/replay/src/util/isBrowser.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { isNodeEnv } from '@sentry/utils'; - -/** - * Returns true if we are in the browser. - */ -export function isBrowser(): boolean { - // eslint-disable-next-line no-restricted-globals - return typeof window !== 'undefined' && (!isNodeEnv() || isElectronNodeRenderer()); -} - -type ElectronProcess = { type?: string }; - -// Electron renderers with nodeIntegration enabled are detected as Node.js so we specifically test for them -function isElectronNodeRenderer(): boolean { - return typeof process !== 'undefined' && (process as ElectronProcess).type === 'renderer'; -} diff --git a/packages/utils/src/node.ts b/packages/utils/src/node.ts index 33bece5ef62f..0094672bda75 100644 --- a/packages/utils/src/node.ts +++ b/packages/utils/src/node.ts @@ -19,6 +19,21 @@ export function isNodeEnv(): boolean { ); } +/** + * Returns true if we are in the browser. + */ +export function isBrowser(): boolean { + // eslint-disable-next-line no-restricted-globals + return typeof window !== 'undefined' && (!isNodeEnv() || isElectronNodeRenderer()); +} + +type ElectronProcess = { type?: string }; + +// Electron renderers with nodeIntegration enabled are detected as Node.js so we specifically test for them +function isElectronNodeRenderer(): boolean { + return typeof process !== 'undefined' && (process as ElectronProcess).type === 'renderer'; +} + /** * Requires a module which is protected against bundler minification. * diff --git a/yarn.lock b/yarn.lock index d52033c73e40..adacf99f05a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4699,85 +4699,91 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== -"@remix-run/node@^1.4.3": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@remix-run/node/-/node-1.5.1.tgz#1c367d4035baaef8f0ea66962a826456d62f0030" - integrity sha512-yl4bd1nl7MiJp4tI3+4ygObeMU3txM4Uo09IdHLRa4NMdBQnacUJ47kqCahny01MerC2JL2d9NPjdVPwRCRZvQ== - dependencies: - "@remix-run/server-runtime" "1.5.1" - "@remix-run/web-fetch" "^4.1.3" - "@remix-run/web-file" "^3.0.2" - "@remix-run/web-stream" "^1.0.3" +"@remix-run/node@^1.19.3": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@remix-run/node/-/node-1.19.3.tgz#d27e2f742fc45379525cb3fca466a883ca06d6c9" + integrity sha512-z5qrVL65xLXIUpU4mkR4MKlMeKARLepgHAk4W5YY3IBXOreRqOGUC70POViYmY7x38c2Ia1NwqL80H+0h7jbMw== + dependencies: + "@remix-run/server-runtime" "1.19.3" + "@remix-run/web-fetch" "^4.3.6" + "@remix-run/web-file" "^3.0.3" + "@remix-run/web-stream" "^1.0.4" "@web3-storage/multipart-parser" "^1.0.0" abort-controller "^3.0.0" cookie-signature "^1.1.0" source-map-support "^0.5.21" stream-slice "^0.1.2" -"@remix-run/react@^1.4.3": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@remix-run/react/-/react-1.5.1.tgz#372e5e80f3f10a638b0567c4e03307dfb0a28dc0" - integrity sha512-p4t6tC/WyPeLW7DO4g7ZSyH9EpWO37c4wD2np3rDwtv3WtsTZ70bU/+NOWE9nv74mH8i1C50eJ3/OR+8Ll8UbA== +"@remix-run/react@^1.19.3": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@remix-run/react/-/react-1.19.3.tgz#00efcc583bf05b434566e56381d51df86575d8b0" + integrity sha512-iP37MZ+oG1n4kv4rX77pKT/knra51lNwKo5tinPPF0SuNJhF3+XjWo5nwEjvisKTXLZ/OHeicinhgX2JHHdDvA== dependencies: - history "^5.3.0" - react-router-dom "^6.2.2" + "@remix-run/router" "1.7.2" + react-router-dom "6.14.2" "@remix-run/router@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.0.2.tgz#1c17eadb2fa77f80a796ad5ea9bf108e6993ef06" integrity sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ== -"@remix-run/server-runtime@1.5.1": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@remix-run/server-runtime/-/server-runtime-1.5.1.tgz#5272b01e6dce109dc10bd68447ceae2d039315b2" - integrity sha512-FQbCCdW+qzE3wpoCwUKdwcL8yZVYNPiyHS9JS/6r6qmd/yvZfbj44E48wEQ6trbWE2TUiEh/EQqNMyrZWEs4bw== +"@remix-run/router@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.2.tgz#cba1cf0a04bc04cb66027c51fa600e9cbc388bc8" + integrity sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A== + +"@remix-run/server-runtime@1.19.3": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@remix-run/server-runtime/-/server-runtime-1.19.3.tgz#206b55337c266c5bc254878f8ff3cd5677cc60fb" + integrity sha512-KzQ+htUsKqpBgKE2tWo7kIIGy3MyHP58Io/itUPvV+weDjApwr9tQr9PZDPA3yAY6rAzLax7BU0NMSYCXWFY5A== dependencies: - "@types/cookie" "^0.4.0" + "@remix-run/router" "1.7.2" + "@types/cookie" "^0.4.1" "@web3-storage/multipart-parser" "^1.0.0" cookie "^0.4.1" - jsesc "^3.0.1" - react-router-dom "^6.2.2" set-cookie-parser "^2.4.8" source-map "^0.7.3" -"@remix-run/web-blob@^3.0.3", "@remix-run/web-blob@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@remix-run/web-blob/-/web-blob-3.0.4.tgz#99c67b9d0fb641bd0c07d267fd218ae5aa4ae5ed" - integrity sha512-AfegzZvSSDc+LwnXV+SwROTrDtoLiPxeFW+jxgvtDAnkuCX1rrzmVJ6CzqZ1Ai0bVfmJadkG5GxtAfYclpPmgw== +"@remix-run/web-blob@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-blob/-/web-blob-3.1.0.tgz#e0c669934c1eb6028960047e57a13ed38bbfb434" + integrity sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g== dependencies: - "@remix-run/web-stream" "^1.0.0" + "@remix-run/web-stream" "^1.1.0" web-encoding "1.1.5" -"@remix-run/web-fetch@^4.1.3": - version "4.1.3" - resolved "https://registry.yarnpkg.com/@remix-run/web-fetch/-/web-fetch-4.1.3.tgz#8ad3077c1b5bd9fe2a8813d0ad3c84970a495c04" - integrity sha512-D3KXAEkzhR248mu7wCHReQrMrIo3Y9pDDa7TrlISnsOEvqkfWkJJF+PQWmOIKpOSHAhDg7TCb2tzvW8lc/MfHw== +"@remix-run/web-fetch@^4.3.6": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@remix-run/web-fetch/-/web-fetch-4.4.1.tgz#1ea34e6f1c660a52e7582007917a552f0efdc58b" + integrity sha512-xMceEGn2kvfeWS91nHSOhEQHPGgjFnmDVpWFZrbWPVdiTByMZIn421/tdSF6Kd1RsNsY+5Iwt3JFEKZHAcMQHw== dependencies: - "@remix-run/web-blob" "^3.0.4" - "@remix-run/web-form-data" "^3.0.2" - "@remix-run/web-stream" "^1.0.3" + "@remix-run/web-blob" "^3.1.0" + "@remix-run/web-file" "^3.1.0" + "@remix-run/web-form-data" "^3.1.0" + "@remix-run/web-stream" "^1.1.0" "@web3-storage/multipart-parser" "^1.0.0" + abort-controller "^3.0.0" data-uri-to-buffer "^3.0.1" mrmime "^1.0.0" -"@remix-run/web-file@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@remix-run/web-file/-/web-file-3.0.2.tgz#1a6cc0900a1310ede4bc96abad77ac6eb27a2131" - integrity sha512-eFC93Onh/rZ5kUNpCQersmBtxedGpaXK2/gsUl49BYSGK/DvuPu3l06vmquEDdcPaEuXcsdGP0L7zrmUqrqo4A== +"@remix-run/web-file@^3.0.3", "@remix-run/web-file@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-file/-/web-file-3.1.0.tgz#07219021a2910e90231bc30ca1ce693d0e9d3825" + integrity sha512-dW2MNGwoiEYhlspOAXFBasmLeYshyAyhIdrlXBi06Duex5tDr3ut2LFKVj7tyHLmn8nnNwFf1BjNbkQpygC2aQ== dependencies: - "@remix-run/web-blob" "^3.0.3" + "@remix-run/web-blob" "^3.1.0" -"@remix-run/web-form-data@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@remix-run/web-form-data/-/web-form-data-3.0.2.tgz#733a4c8f8176523b7b60a8bd0dc6704fd4d498f3" - integrity sha512-F8tm3iB1sPxMpysK6Js7lV3gvLfTNKGmIW38t/e6dtPEB5L1WdbRG1cmLyhsonFc7rT1x1JKdz+2jCtoSdnIUw== +"@remix-run/web-form-data@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-form-data/-/web-form-data-3.1.0.tgz#47f9ad8ce8bf1c39ed83eab31e53967fe8e3df6a" + integrity sha512-NdeohLMdrb+pHxMQ/Geuzdp0eqPbea+Ieo8M8Jx2lGC6TBHsgHzYcBvr0LyPdPVycNRDEpWpiDdCOdCryo3f9A== dependencies: web-encoding "1.1.5" -"@remix-run/web-stream@^1.0.0", "@remix-run/web-stream@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@remix-run/web-stream/-/web-stream-1.0.3.tgz#3284a6a45675d1455c4d9c8f31b89225c9006438" - integrity sha512-wlezlJaA5NF6SsNMiwQnnAW6tnPzQ5I8qk0Y0pSohm0eHKa2FQ1QhEKLVVcDDu02TmkfHgnux0igNfeYhDOXiA== +"@remix-run/web-stream@^1.0.4", "@remix-run/web-stream@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-stream/-/web-stream-1.1.0.tgz#b93a8f806c2c22204930837c44d81fdedfde079f" + integrity sha512-KRJtwrjRV5Bb+pM7zxcTJkhIqWWSy+MYsIxHK+0m5atcznsf15YwUBWHWulZerV2+vvHH1Lp1DD7pw6qKW8SgA== dependencies: web-streams-polyfill "^3.1.1" @@ -5459,7 +5465,7 @@ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.5.2.tgz#9bf9d62c838c85a07c92fdf2334c2c14fd9c59a9" integrity sha512-DBpRoJGKJZn7RY92dPrgoMew8xCWc2P71beqsjyhEI/Ds9mOyVmBwtekyfhpwFIVt1WrxTonFifiOZ62V8CnNA== -"@types/cookie@^0.4.0", "@types/cookie@^0.4.1": +"@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== @@ -16520,7 +16526,7 @@ history@^4.6.0, history@^4.9.0: tiny-warning "^1.0.0" value-equal "^1.0.1" -history@^5.2.0, history@^5.3.0: +history@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== @@ -17265,6 +17271,11 @@ ip-regex@^2.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= +ip-regex@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" + integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== + ip@1.1.5, ip@^1.1.0, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -17566,6 +17577,13 @@ is-interactive@^2.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-2.0.0.tgz#40c57614593826da1100ade6059778d597f16e90" integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== +is-ip@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-3.1.0.tgz#2ae5ddfafaf05cb8008a62093cf29734f657c5d8" + integrity sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q== + dependencies: + ip-regex "^4.0.0" + is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" @@ -18713,11 +18731,6 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsesc@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" - integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== - jsesc@~0.3.x: version "0.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.3.0.tgz#1bf5ee63b4539fe2e26d0c1e99c240b97a457972" @@ -25438,21 +25451,27 @@ react-refresh@0.8.3: dependencies: "@remix-run/router" "1.0.2" -"react-router-6@npm:react-router@6.3.0", react-router@6.3.0: - name react-router-6 +"react-router-6@npm:react-router@6.3.0": version "6.3.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== dependencies: history "^5.2.0" -react-router-dom@^6.2.2: - version "6.3.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d" - integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw== +react-router-dom@6.14.2: + version "6.14.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.2.tgz#88f520118b91aa60233bd08dbd3fdcaea3a68488" + integrity sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg== dependencies: - history "^5.2.0" - react-router "6.3.0" + "@remix-run/router" "1.7.2" + react-router "6.14.2" + +react-router@6.14.2: + version "6.14.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.2.tgz#1f60994d8c369de7b8ba7a78d8f7ec23df76b300" + integrity sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ== + dependencies: + "@remix-run/router" "1.7.2" react@^18.0.0: version "18.0.0"