mirror of
https://github.com/papra-hq/papra.git
synced 2025-12-21 12:09:39 -06:00
refactor(webhooks): updated webhooks signatures and payload to match standard-webhook spec (#430)
This commit is contained in:
committed by
GitHub
parent
67b3b14cdf
commit
a8cff8cedc
5
.changeset/bumpy-aliens-juggle.md
Normal file
5
.changeset/bumpy-aliens-juggle.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@papra/webhooks": minor
|
||||
---
|
||||
|
||||
Breaking change: updated webhooks signatures and payload format to match standard-webhook spec
|
||||
@@ -42,12 +42,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@corentinth/chisels": "^1.3.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"ofetch": "^1.4.1",
|
||||
"tsee": "^1.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"standardwebhooks": "^1.0.0",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
|
||||
@@ -2,7 +2,25 @@ export function createInvalidSignatureError() {
|
||||
return Object.assign(
|
||||
new Error('[Papra Webhooks] Invalid signature'),
|
||||
{
|
||||
code: 'INVALID_SIGNATURE',
|
||||
code: 'webhook.invalid_signature',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function createUnsupportedSignatureVersionError() {
|
||||
return Object.assign(
|
||||
new Error('[Papra Webhooks] Unsupported signature version, supported versions are "v1"'),
|
||||
{
|
||||
code: 'webhook.unsupported_signature_version',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function createInvalidSignatureFormatError() {
|
||||
return Object.assign(
|
||||
new Error('[Papra Webhooks] Invalid signature format, unprocessable signature'),
|
||||
{
|
||||
code: 'webhook.invalid_signature_format',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,45 @@
|
||||
import type { BuildWebhookEventPayload, WebhookEvents, WebhookPayloads } from '../webhooks.types';
|
||||
import type { StandardWebhookEventPayload, WebhookEvents } from '../webhooks.types';
|
||||
import { EventEmitter } from 'tsee';
|
||||
import { verifySignature } from '../signature';
|
||||
import { parseBody } from '../webhooks.models';
|
||||
import { createInvalidSignatureError } from './handler.errors';
|
||||
|
||||
function handleError({ error }: { error: unknown }) {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw createInvalidSignatureError();
|
||||
}
|
||||
|
||||
export function createWebhooksHandler({
|
||||
secret,
|
||||
onInvalidSignature = () => {
|
||||
createInvalidSignatureError();
|
||||
},
|
||||
onError = handleError,
|
||||
}: {
|
||||
secret: string;
|
||||
onInvalidSignature?: ({ bodyBuffer, signature }: { bodyBuffer: ArrayBuffer; signature: string }) => void | Promise<void>;
|
||||
onError?: (args: { body: string; signature: string; webhookId: string; timestamp: string; error: unknown }) => void | Promise<void>;
|
||||
}) {
|
||||
const eventEmitter = new EventEmitter<WebhookEvents & { '*': (payload: BuildWebhookEventPayload<WebhookPayloads>) => void }>();
|
||||
const eventEmitter = new EventEmitter<WebhookEvents & { '*': (payload: StandardWebhookEventPayload) => void }>();
|
||||
|
||||
return {
|
||||
on: eventEmitter.on,
|
||||
ee: eventEmitter,
|
||||
handle: async ({ bodyBuffer, signature }: { bodyBuffer: ArrayBuffer; signature: string }) => {
|
||||
const isValid = await verifySignature({ bodyBuffer, signature, secret });
|
||||
handle: async ({ body, signature, webhookId, timestamp }: { body: string; signature: string; webhookId: string; timestamp: string }) => {
|
||||
try {
|
||||
const isValid = await verifySignature({ serializedPayload: body, signature, secret, webhookId, timestamp });
|
||||
|
||||
if (!isValid) {
|
||||
await onInvalidSignature({ bodyBuffer, signature });
|
||||
return;
|
||||
if (!isValid) {
|
||||
throw createInvalidSignatureError();
|
||||
}
|
||||
|
||||
const parsedBody = parseBody(body);
|
||||
const { type } = parsedBody;
|
||||
|
||||
eventEmitter.emit(type, parsedBody as any);
|
||||
eventEmitter.emit('*', parsedBody);
|
||||
} catch (error) {
|
||||
await onError({ body, signature, webhookId, timestamp, error });
|
||||
}
|
||||
|
||||
const payload = parseBody(bodyBuffer.toString());
|
||||
const { event } = payload;
|
||||
|
||||
eventEmitter.emit(event, payload as any);
|
||||
eventEmitter.emit('*', payload);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { createWebhooksHandler } from './handler/handler.services';
|
||||
export { EVENT_NAMES, type EventName } from './webhooks.constants';
|
||||
export { triggerWebhook } from './webhooks.services';
|
||||
export type { WebhookEventPayload, WebhookEvents, WebhookPayload, WebhookPayloads } from './webhooks.types';
|
||||
export type { StandardWebhookEventPayload, WebhookEvents, WebhookPayload, WebhookPayloads } from './webhooks.types';
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { Buffer } from 'node:buffer';
|
||||
import { Webhook } from 'standardwebhooks';
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { createInvalidSignatureFormatError, createUnsupportedSignatureVersionError } from './handler/handler.errors';
|
||||
import { arrayBufferToBase64, base64ToArrayBuffer, signBody, verifySignature } from './signature';
|
||||
|
||||
const arrayBuffer = (str: string) => new TextEncoder().encode(str).buffer as ArrayBuffer;
|
||||
@@ -6,25 +9,103 @@ const arrayBuffer = (str: string) => new TextEncoder().encode(str).buffer as Arr
|
||||
describe('signature', () => {
|
||||
describe('signBody', () => {
|
||||
test('a buffer can be signed with a secret, the resulting signature is a base64 encoded string', async () => {
|
||||
const bodyBuffer = arrayBuffer('test');
|
||||
const payload = { event: 'foo.bar', payload: { biz: 'baz' }, now: new Date('2025-07-25') };
|
||||
const serializedPayload = JSON.stringify(payload);
|
||||
const webhookId = 'msg_a1hm8ojetdjhf5boqd2hz244';
|
||||
const timestamp = '1753390766';
|
||||
const secret = 'secret-key';
|
||||
|
||||
const { signature } = await signBody({ bodyBuffer, secret });
|
||||
const { signature } = await signBody({ serializedPayload, webhookId, timestamp, secret });
|
||||
|
||||
expect(signature).to.equal('2yIt56m6njKnw7VCoPEYRQE1jSIxyuYutt8/c1ezh9M=');
|
||||
expect(signature).to.equal('v1,POSJo83MmyWmTh3NJOtEpBZSn+CmdpjHSS05p3wYAVE=');
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifySignature', () => {
|
||||
test('verify that the signature of a buffer has been created with a given secret', async () => {
|
||||
const bodyBuffer = arrayBuffer('test');
|
||||
const payload = { event: 'foo.bar', payload: { biz: 'baz' }, now: new Date('2025-07-25') };
|
||||
const serializedPayload = JSON.stringify(payload);
|
||||
const webhookId = 'msg_a1hm8ojetdjhf5boqd2hz244';
|
||||
const timestamp = '1753390766';
|
||||
const secret = 'secret-key';
|
||||
const signature = '2yIt56m6njKnw7VCoPEYRQE1jSIxyuYutt8/c1ezh9M=';
|
||||
const signature = 'v1,POSJo83MmyWmTh3NJOtEpBZSn+CmdpjHSS05p3wYAVE=';
|
||||
|
||||
const result = await verifySignature({ bodyBuffer, signature, secret });
|
||||
const result = await verifySignature({ serializedPayload, webhookId, timestamp, signature, secret });
|
||||
|
||||
expect(result).to.equal(true);
|
||||
});
|
||||
|
||||
test('an error is thrown when the version is not supported', async () => {
|
||||
const payload = { event: 'foo.bar', payload: { biz: 'baz' }, now: new Date('2025-07-25') };
|
||||
const serializedPayload = JSON.stringify(payload);
|
||||
const webhookId = 'msg_a1hm8ojetdjhf5boqd2hz244';
|
||||
const timestamp = '1753390766';
|
||||
const secret = 'secret-key';
|
||||
const signature = 'v2,POSJo83MmyWmTh3NJOtEpBZSn+CmdpjHSS05p3wYAVE=';
|
||||
|
||||
expect(verifySignature({ serializedPayload, webhookId, timestamp, signature, secret })).rejects.toThrow(createUnsupportedSignatureVersionError());
|
||||
});
|
||||
|
||||
test('an error is thrown when the signature is not valid', async () => {
|
||||
const payload = { event: 'foo.bar', payload: { biz: 'baz' }, now: new Date('2025-07-25') };
|
||||
const serializedPayload = JSON.stringify(payload);
|
||||
const webhookId = 'msg_a1hm8ojetdjhf5boqd2hz244';
|
||||
const timestamp = '1753390766';
|
||||
const secret = 'secret-key';
|
||||
const signature = '';
|
||||
|
||||
expect(verifySignature({ serializedPayload, webhookId, timestamp, signature, secret })).rejects.toThrow(createInvalidSignatureFormatError());
|
||||
});
|
||||
});
|
||||
|
||||
describe('standardwebhooks compatibility', () => {
|
||||
// Because standardwebhooks uses hardcoded Date.now()
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('a signed payload can be verified using the "standardwebhooks" package', async () => {
|
||||
const payload = { event: 'foo.bar', payload: { biz: 'baz' }, now: new Date('2025-07-25') };
|
||||
const serializedPayload = JSON.stringify(payload);
|
||||
const webhookId = 'msg_a1hm8ojetdjhf5boqd2hz244';
|
||||
const timestamp = '1753390766';
|
||||
const secret = 'secret-key';
|
||||
|
||||
// Because standardwebhooks uses hardcoded Date.now() to check for webhook expiration...
|
||||
vi.setSystemTime(new Date(Number(timestamp) * 1000));
|
||||
|
||||
const webhook = new Webhook(Buffer.from(secret).toString('base64'));
|
||||
|
||||
const result = await webhook.verify(serializedPayload, {
|
||||
'webhook-id': webhookId,
|
||||
'webhook-timestamp': timestamp,
|
||||
'webhook-signature': 'v1,POSJo83MmyWmTh3NJOtEpBZSn+CmdpjHSS05p3wYAVE=',
|
||||
});
|
||||
|
||||
expect(result).to.eql({
|
||||
event: 'foo.bar',
|
||||
payload: { biz: 'baz' },
|
||||
now: '2025-07-25T00:00:00.000Z',
|
||||
});
|
||||
});
|
||||
|
||||
test('the signature is the same as the one generated by the "standardwebhooks" package', async () => {
|
||||
const payload = { event: 'foo.bar', payload: { biz: 'baz' }, now: new Date('2025-07-25') };
|
||||
const serializedPayload = JSON.stringify(payload);
|
||||
const webhookId = 'msg_a1hm8ojetdjhf5boqd2hz244';
|
||||
const timestamp = '1753390766';
|
||||
const secret = 'secret-key';
|
||||
|
||||
const { signature } = await signBody({ serializedPayload, webhookId, timestamp, secret });
|
||||
|
||||
const standardWebhookSignature = new Webhook(Buffer.from(secret).toString('base64')).sign(webhookId, new Date(Number(timestamp) * 1000), serializedPayload);
|
||||
|
||||
expect(standardWebhookSignature).to.equal(signature);
|
||||
});
|
||||
});
|
||||
|
||||
describe('arrayBufferToBase64', () => {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { createInvalidSignatureFormatError, createUnsupportedSignatureVersionError } from './handler/handler.errors';
|
||||
|
||||
const WEBHOOK_SIGNATURE_HMAC_VERSION = 'v1';
|
||||
|
||||
export function arrayBufferToBase64(arrayBuffer: ArrayBuffer) {
|
||||
return btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
|
||||
}
|
||||
@@ -6,37 +10,74 @@ export function base64ToArrayBuffer(base64: string) {
|
||||
return new Uint8Array(atob(base64).split('').map(char => char.charCodeAt(0))).buffer;
|
||||
}
|
||||
|
||||
function createSignaturePayload({
|
||||
serializedPayload,
|
||||
webhookId,
|
||||
timestamp,
|
||||
}: {
|
||||
serializedPayload: string;
|
||||
webhookId: string;
|
||||
timestamp: string;
|
||||
}) {
|
||||
return `${webhookId}.${timestamp}.${serializedPayload}`;
|
||||
}
|
||||
|
||||
async function hmacSign({ secret, payload }: { secret: string; payload: string }) {
|
||||
const encoder = new TextEncoder();
|
||||
const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
||||
return crypto.subtle.sign('HMAC', key, encoder.encode(payload));
|
||||
}
|
||||
|
||||
export async function signBody({
|
||||
bodyBuffer,
|
||||
serializedPayload,
|
||||
webhookId,
|
||||
timestamp,
|
||||
secret,
|
||||
}: {
|
||||
bodyBuffer: ArrayBuffer;
|
||||
serializedPayload: string;
|
||||
webhookId: string;
|
||||
timestamp: string;
|
||||
secret: string;
|
||||
}) {
|
||||
const encoder = new TextEncoder();
|
||||
const keyData = encoder.encode(secret);
|
||||
const key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
||||
const payload = createSignaturePayload({ serializedPayload, webhookId, timestamp });
|
||||
|
||||
const signature = await crypto.subtle.sign('HMAC', key, bodyBuffer);
|
||||
const signatureBase64 = arrayBufferToBase64(signature);
|
||||
const rawSignature = await hmacSign({ secret, payload });
|
||||
const signatureBase64 = arrayBufferToBase64(rawSignature);
|
||||
const signature = `${WEBHOOK_SIGNATURE_HMAC_VERSION},${signatureBase64}`;
|
||||
|
||||
return { signature: signatureBase64 };
|
||||
return { signature };
|
||||
}
|
||||
|
||||
export async function verifySignature({
|
||||
bodyBuffer,
|
||||
serializedPayload,
|
||||
webhookId,
|
||||
timestamp,
|
||||
signature: base64Signature,
|
||||
secret,
|
||||
}: {
|
||||
bodyBuffer: ArrayBuffer;
|
||||
serializedPayload: string;
|
||||
webhookId: string;
|
||||
timestamp: string;
|
||||
signature: string;
|
||||
secret: string;
|
||||
}): Promise<boolean> {
|
||||
const [version, signature] = base64Signature.split(',', 2);
|
||||
|
||||
if (!signature || !version) {
|
||||
throw createInvalidSignatureFormatError();
|
||||
}
|
||||
|
||||
if (version !== WEBHOOK_SIGNATURE_HMAC_VERSION) {
|
||||
throw createUnsupportedSignatureVersionError();
|
||||
}
|
||||
|
||||
const payload = createSignaturePayload({ serializedPayload, webhookId, timestamp });
|
||||
|
||||
const signatureBuffer = base64ToArrayBuffer(signature);
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const keyData = encoder.encode(secret);
|
||||
const key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
|
||||
|
||||
const signatureBuffer = base64ToArrayBuffer(base64Signature);
|
||||
|
||||
return crypto.subtle.verify('HMAC', key, signatureBuffer, bodyBuffer);
|
||||
return crypto.subtle.verify('HMAC', key, signatureBuffer, encoder.encode(payload));
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import type { WebhookEventPayload, WebhookPayloads } from './webhooks.types';
|
||||
import type { StandardWebhookEventPayload, WebhookPayloads } from './webhooks.types';
|
||||
|
||||
export function serializeBody({ now = new Date(), ...payload }: { now?: Date } & WebhookPayloads) {
|
||||
const body: WebhookEventPayload = {
|
||||
...payload,
|
||||
timestampMs: now.getTime(),
|
||||
export function serializeBody<T extends WebhookPayloads>({ now = new Date(), payload, event }: { now?: Date; payload: T['payload']; event: T['event'] }) {
|
||||
const body: StandardWebhookEventPayload = {
|
||||
data: payload,
|
||||
type: event,
|
||||
timestamp: now.toISOString(),
|
||||
};
|
||||
|
||||
return JSON.stringify(body);
|
||||
}
|
||||
|
||||
export function parseBody(body: string) {
|
||||
return JSON.parse(body) as WebhookEventPayload;
|
||||
return JSON.parse(body) as StandardWebhookEventPayload;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { WebhookPayloads } from './webhooks.types';
|
||||
import { createId } from '@paralleldrive/cuid2';
|
||||
import { ofetch } from 'ofetch';
|
||||
import { signBody } from './signature';
|
||||
import { serializeBody } from './webhooks.models';
|
||||
@@ -9,7 +10,7 @@ export async function webhookHttpClient({
|
||||
}: {
|
||||
url: string;
|
||||
method: string;
|
||||
body: ArrayBuffer;
|
||||
body: string;
|
||||
headers: Record<string, string>;
|
||||
}) {
|
||||
const response = await ofetch.raw<unknown>(url, {
|
||||
@@ -23,39 +24,43 @@ export async function webhookHttpClient({
|
||||
};
|
||||
}
|
||||
|
||||
export async function triggerWebhook({
|
||||
export async function triggerWebhook<T extends WebhookPayloads>({
|
||||
webhookUrl,
|
||||
webhookSecret,
|
||||
httpClient = webhookHttpClient,
|
||||
now = new Date(),
|
||||
...payload
|
||||
|
||||
payload,
|
||||
event,
|
||||
webhookId = `msg_${createId()}`,
|
||||
}: {
|
||||
webhookUrl: string;
|
||||
webhookSecret?: string | null;
|
||||
httpClient?: typeof webhookHttpClient;
|
||||
payload: T['payload'];
|
||||
now?: Date;
|
||||
} & WebhookPayloads) {
|
||||
const { event } = payload;
|
||||
event: T['event'];
|
||||
webhookId?: string;
|
||||
}) {
|
||||
const timestamp = Math.floor(now.getTime() / 1000).toString();
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'User-Agent': 'papra-webhook-client',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Event': event,
|
||||
'user-agent': 'papra-webhook-client',
|
||||
'content-type': 'application/json',
|
||||
'webhook-id': webhookId,
|
||||
'webhook-timestamp': timestamp,
|
||||
};
|
||||
|
||||
const body = serializeBody({ ...payload, now });
|
||||
const bodyBuffer = new TextEncoder().encode(body).buffer as ArrayBuffer;
|
||||
const body = serializeBody({ event, payload, now });
|
||||
|
||||
if (webhookSecret) {
|
||||
const { signature } = await signBody({ bodyBuffer, secret: webhookSecret });
|
||||
headers['X-Signature'] = signature;
|
||||
const { signature } = await signBody({ serializedPayload: body, webhookId, timestamp, secret: webhookSecret });
|
||||
headers['webhook-signature'] = signature;
|
||||
}
|
||||
|
||||
const { responseData, responseStatus } = await httpClient({
|
||||
url: webhookUrl,
|
||||
method: 'POST',
|
||||
body: bodyBuffer,
|
||||
body,
|
||||
headers,
|
||||
});
|
||||
|
||||
|
||||
@@ -24,11 +24,11 @@ export type DocumentDeletedPayload = WebhookPayload<
|
||||
export type WebhookPayloads = DocumentCreatedPayload | DocumentDeletedPayload;
|
||||
|
||||
type ExtractEventName<T> = T extends WebhookPayload<infer E, any> ? E : never;
|
||||
export type BuildWebhookEventPayload<T> = T & { timestampMs: number };
|
||||
export type BuildStandardWebhookEventPayload<T extends WebhookPayloads> = { type: T['event']; timestamp: string; data: T['payload'] };
|
||||
export type BuildWebhookEvents<T extends WebhookPayloads> = {
|
||||
[K in ExtractEventName<T>]: (args: BuildWebhookEventPayload<Extract<T, WebhookPayload<K, any>>>) => void;
|
||||
[K in ExtractEventName<T>]: (args: BuildStandardWebhookEventPayload<Extract<T, WebhookPayload<K, any>>>) => void;
|
||||
};
|
||||
|
||||
export type WebhookEvents = BuildWebhookEvents<WebhookPayloads>;
|
||||
|
||||
export type WebhookEventPayload = BuildWebhookEventPayload<WebhookPayloads>;
|
||||
export type StandardWebhookEventPayload = BuildStandardWebhookEventPayload<WebhookPayloads>;
|
||||
|
||||
68
pnpm-lock.yaml
generated
68
pnpm-lock.yaml
generated
@@ -15,6 +15,9 @@ catalogs:
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^3.0.2
|
||||
version: 3.2.4
|
||||
better-auth:
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8
|
||||
eslint:
|
||||
specifier: ^9.30.1
|
||||
version: 9.30.1
|
||||
@@ -527,6 +530,9 @@ importers:
|
||||
'@corentinth/chisels':
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.1
|
||||
'@paralleldrive/cuid2':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
ofetch:
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1
|
||||
@@ -540,6 +546,9 @@ importers:
|
||||
eslint:
|
||||
specifier: 'catalog:'
|
||||
version: 9.30.1(jiti@2.4.2)
|
||||
standardwebhooks:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.8.3
|
||||
@@ -2633,10 +2642,6 @@ packages:
|
||||
'@noble/ciphers@0.6.0':
|
||||
resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==}
|
||||
|
||||
'@noble/hashes@1.6.1':
|
||||
resolution: {integrity: sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==}
|
||||
engines: {node: ^14.21.3 || >=16}
|
||||
|
||||
'@noble/hashes@1.8.0':
|
||||
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
|
||||
engines: {node: ^14.21.3 || >=16}
|
||||
@@ -3257,6 +3262,9 @@ packages:
|
||||
peerDependencies:
|
||||
solid-js: ^1.8.6
|
||||
|
||||
'@stablelib/base64@1.0.1':
|
||||
resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==}
|
||||
|
||||
'@stylistic/eslint-plugin@2.13.0':
|
||||
resolution: {integrity: sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -5133,6 +5141,9 @@ packages:
|
||||
fast-levenshtein@2.0.6:
|
||||
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
||||
|
||||
fast-sha256@1.3.0:
|
||||
resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==}
|
||||
|
||||
fast-uri@3.0.6:
|
||||
resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
|
||||
|
||||
@@ -5906,9 +5917,6 @@ packages:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
loupe@3.1.3:
|
||||
resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
|
||||
|
||||
loupe@3.1.4:
|
||||
resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==}
|
||||
|
||||
@@ -7297,6 +7305,9 @@ packages:
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
standardwebhooks@1.0.0:
|
||||
resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==}
|
||||
|
||||
starlight-links-validator@0.16.0:
|
||||
resolution: {integrity: sha512-wInToor19C7UxhesPuxTBIhB1LH1wzNQHD4HaumfcB+yFhg5u80yQEnkZDrABHrUEEEwFm//NoZbWhnUj1m2ug==}
|
||||
engines: {node: '>=18.17.1'}
|
||||
@@ -10117,6 +10128,12 @@ snapshots:
|
||||
eslint: 9.27.0(jiti@2.4.2)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/eslint-utils@4.5.1(eslint@9.30.1(jiti@2.4.2))':
|
||||
dependencies:
|
||||
eslint: 9.30.1(jiti@2.4.2)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.7.0(eslint@9.27.0(jiti@2.4.2))':
|
||||
dependencies:
|
||||
eslint: 9.27.0(jiti@2.4.2)
|
||||
@@ -10689,8 +10706,6 @@ snapshots:
|
||||
|
||||
'@noble/ciphers@0.6.0': {}
|
||||
|
||||
'@noble/hashes@1.6.1': {}
|
||||
|
||||
'@noble/hashes@1.8.0': {}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
@@ -10734,7 +10749,7 @@ snapshots:
|
||||
|
||||
'@paralleldrive/cuid2@2.2.2':
|
||||
dependencies:
|
||||
'@noble/hashes': 1.6.1
|
||||
'@noble/hashes': 1.8.0
|
||||
|
||||
'@pdfslick/core@2.3.0(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1))':
|
||||
dependencies:
|
||||
@@ -11410,6 +11425,8 @@ snapshots:
|
||||
dependencies:
|
||||
solid-js: 1.9.7
|
||||
|
||||
'@stablelib/base64@1.0.1': {}
|
||||
|
||||
'@stylistic/eslint-plugin@2.13.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/utils': 8.21.0(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3)
|
||||
@@ -12695,7 +12712,7 @@ snapshots:
|
||||
assertion-error: 2.0.1
|
||||
check-error: 2.1.1
|
||||
deep-eql: 5.0.2
|
||||
loupe: 3.1.3
|
||||
loupe: 3.1.4
|
||||
pathval: 2.0.0
|
||||
|
||||
chalk@4.1.2:
|
||||
@@ -13456,15 +13473,15 @@ snapshots:
|
||||
|
||||
eslint-plugin-astro@1.3.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2))
|
||||
'@jridgewell/sourcemap-codec': 1.5.4
|
||||
'@typescript-eslint/types': 8.35.1
|
||||
'@eslint-community/eslint-utils': 4.5.1(eslint@9.30.1(jiti@2.4.2))
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
'@typescript-eslint/types': 8.26.1
|
||||
astro-eslint-parser: 1.1.0(typescript@5.8.3)
|
||||
eslint: 9.30.1(jiti@2.4.2)
|
||||
eslint-compat-utils: 0.6.5(eslint@9.30.1(jiti@2.4.2))
|
||||
eslint-compat-utils: 0.6.4(eslint@9.30.1(jiti@2.4.2))
|
||||
globals: 15.15.0
|
||||
postcss: 8.5.6
|
||||
postcss-selector-parser: 7.1.0
|
||||
postcss: 8.5.3
|
||||
postcss-selector-parser: 7.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
@@ -13779,7 +13796,7 @@ snapshots:
|
||||
debug: 4.4.1
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint: 9.30.1(jiti@2.4.2)
|
||||
eslint-compat-utils: 0.6.4(eslint@9.30.1(jiti@2.4.2))
|
||||
eslint-compat-utils: 0.6.5(eslint@9.30.1(jiti@2.4.2))
|
||||
natural-compare: 1.4.0
|
||||
yaml-eslint-parser: 1.3.0
|
||||
transitivePeerDependencies:
|
||||
@@ -13867,11 +13884,11 @@ snapshots:
|
||||
'@eslint/core': 0.14.0
|
||||
'@eslint/eslintrc': 3.3.1
|
||||
'@eslint/js': 9.30.1
|
||||
'@eslint/plugin-kit': 0.3.1
|
||||
'@eslint/plugin-kit': 0.3.3
|
||||
'@humanfs/node': 0.16.6
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.2
|
||||
'@types/estree': 1.0.7
|
||||
'@types/estree': 1.0.8
|
||||
'@types/json-schema': 7.0.15
|
||||
ajv: 6.12.6
|
||||
chalk: 4.1.2
|
||||
@@ -14012,6 +14029,8 @@ snapshots:
|
||||
|
||||
fast-levenshtein@2.0.6: {}
|
||||
|
||||
fast-sha256@1.3.0: {}
|
||||
|
||||
fast-uri@3.0.6: {}
|
||||
|
||||
fast-xml-parser@4.4.1:
|
||||
@@ -14719,7 +14738,7 @@ snapshots:
|
||||
|
||||
istanbul-lib-source-maps@5.0.6:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
'@jridgewell/trace-mapping': 0.3.29
|
||||
debug: 4.4.1
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
transitivePeerDependencies:
|
||||
@@ -14930,8 +14949,6 @@ snapshots:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
loupe@3.1.3: {}
|
||||
|
||||
loupe@3.1.4: {}
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
@@ -16754,6 +16771,11 @@ snapshots:
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
standardwebhooks@1.0.0:
|
||||
dependencies:
|
||||
'@stablelib/base64': 1.0.1
|
||||
fast-sha256: 1.3.0
|
||||
|
||||
starlight-links-validator@0.16.0(@astrojs/starlight@0.34.3(astro@5.8.0(@azure/storage-blob@12.27.0)(@types/node@24.0.10)(idb-keyval@6.2.1)(jiti@2.4.2)(rollup@4.39.0)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))):
|
||||
dependencies:
|
||||
'@astrojs/starlight': 0.34.3(astro@5.8.0(@azure/storage-blob@12.27.0)(@types/node@24.0.10)(idb-keyval@6.2.1)(jiti@2.4.2)(rollup@4.39.0)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))
|
||||
|
||||
Reference in New Issue
Block a user