@revealui/core 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/client/admin/components/AdminDashboard.d.ts +7 -1
- package/dist/client/admin/components/AdminDashboard.d.ts.map +1 -1
- package/dist/client/admin/components/AdminDashboard.js +13 -16
- package/dist/client/admin/components/CollectionList.d.ts +2 -1
- package/dist/client/admin/components/CollectionList.d.ts.map +1 -1
- package/dist/client/admin/components/CollectionList.js +5 -5
- package/dist/client/admin/components/DocumentForm.d.ts +2 -1
- package/dist/client/admin/components/DocumentForm.d.ts.map +1 -1
- package/dist/client/admin/components/DocumentForm.js +16 -16
- package/dist/client/admin/components/GlobalForm.d.ts +2 -1
- package/dist/client/admin/components/GlobalForm.d.ts.map +1 -1
- package/dist/client/admin/components/GlobalForm.js +3 -3
- package/dist/client/admin/context/ServerFunctionContext.d.ts +1 -1
- package/dist/client/admin/context/ServerFunctionContext.d.ts.map +1 -1
- package/dist/client/admin/layout.d.ts +1 -1
- package/dist/client/admin/layout.d.ts.map +1 -1
- package/dist/client/admin/layout.js +5 -1
- package/dist/client/admin/page.d.ts +2 -2
- package/dist/client/admin/page.d.ts.map +1 -1
- package/dist/client/admin/page.js +10 -3
- package/dist/client/admin/utils/apiClient.d.ts.map +1 -1
- package/dist/client/admin/utils/apiClient.js +2 -12
- package/dist/client/admin/utils/csrf.d.ts +25 -0
- package/dist/client/admin/utils/csrf.d.ts.map +1 -0
- package/dist/client/admin/utils/csrf.js +51 -0
- package/dist/client/admin/utils/index.d.ts +1 -0
- package/dist/client/admin/utils/index.d.ts.map +1 -1
- package/dist/client/admin/utils/index.js +1 -0
- package/dist/client/richtext/RichTextEditor.d.ts +1 -1
- package/dist/client/richtext/RichTextEditor.d.ts.map +1 -1
- package/dist/client/richtext/components/ImageUploadButton.d.ts.map +1 -1
- package/dist/client/richtext/components/ImageUploadButton.js +2 -5
- package/dist/client/richtext/components/upload.d.ts +21 -0
- package/dist/client/richtext/components/upload.d.ts.map +1 -0
- package/dist/client/richtext/components/upload.js +29 -0
- package/dist/client/richtext/plugins/CollaborationPlugin.d.ts +1 -1
- package/dist/client/richtext/plugins/CollaborationPlugin.d.ts.map +1 -1
- package/dist/client/richtext/plugins/ToolbarPlugin.d.ts +1 -1
- package/dist/client/richtext/plugins/ToolbarPlugin.d.ts.map +1 -1
- package/dist/nextjs/withRevealUI.d.ts +7 -2
- package/dist/nextjs/withRevealUI.d.ts.map +1 -1
- package/dist/nextjs/withRevealUI.js +9 -99
- package/dist/revforge-license.d.ts +4 -2
- package/dist/revforge-license.d.ts.map +1 -1
- package/dist/revforge-license.js +21 -3
- package/dist/richtext/exports/server/rsc.d.ts +3 -3
- package/dist/richtext/exports/server/rsc.d.ts.map +1 -1
- package/dist/storage/_sigv4.d.ts +76 -0
- package/dist/storage/_sigv4.d.ts.map +1 -0
- package/dist/storage/_sigv4.js +134 -0
- package/dist/storage/_xml.d.ts +26 -0
- package/dist/storage/_xml.d.ts.map +1 -0
- package/dist/storage/_xml.js +75 -0
- package/dist/storage/r2.d.ts +14 -2
- package/dist/storage/r2.d.ts.map +1 -1
- package/dist/storage/r2.js +107 -44
- package/package.json +22 -23
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../../../src/richtext/exports/server/rsc.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../../../src/richtext/exports/server/rsc.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Signature Version 4 signer for the Cloudflare R2 (S3-compatible) API.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the request signing that @aws-sdk/client-s3 used to do for us.
|
|
5
|
+
* Uses only node:crypto — no external dependencies, no regex.
|
|
6
|
+
*
|
|
7
|
+
* The core (`computeSigV4`) is verified byte-for-byte against the official AWS
|
|
8
|
+
* SigV4 test-suite `get-vanilla` vector in __tests__/_sigv4.test.ts.
|
|
9
|
+
*
|
|
10
|
+
* Server-only. Do NOT import from client-side code or edge runtime.
|
|
11
|
+
*/
|
|
12
|
+
/** Hex SHA-256 of the empty string — the payload hash for body-less requests. */
|
|
13
|
+
export declare const EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
14
|
+
/** Hex SHA-256 of arbitrary bytes (request payload hashing). */
|
|
15
|
+
export declare function sha256Hex(data: string | Uint8Array): string;
|
|
16
|
+
/**
|
|
17
|
+
* RFC 3986 percent-encoding per the AWS SigV4 spec. `encodeSlash` is false for
|
|
18
|
+
* path segments (slashes are separators) and true for query keys/values.
|
|
19
|
+
*/
|
|
20
|
+
export declare function awsUriEncode(input: string, encodeSlash: boolean): string;
|
|
21
|
+
/** Inputs to the verified SigV4 core. All path/header values must be final. */
|
|
22
|
+
export interface CanonicalInput {
|
|
23
|
+
method: string;
|
|
24
|
+
/** Already AWS-URI-encoded path (slashes preserved). */
|
|
25
|
+
canonicalPath: string;
|
|
26
|
+
/** Raw (unencoded) query pairs; encoded + sorted by the signer. */
|
|
27
|
+
query: [string, string][];
|
|
28
|
+
/** Exact header set to sign (any case; lowercased + trimmed internally). */
|
|
29
|
+
headersToSign: Record<string, string>;
|
|
30
|
+
payloadHash: string;
|
|
31
|
+
amzDate: string;
|
|
32
|
+
region: string;
|
|
33
|
+
service: string;
|
|
34
|
+
accessKeyId: string;
|
|
35
|
+
secretAccessKey: string;
|
|
36
|
+
}
|
|
37
|
+
export interface SigV4Result {
|
|
38
|
+
signature: string;
|
|
39
|
+
signedHeaders: string;
|
|
40
|
+
scope: string;
|
|
41
|
+
canonicalQuery: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The verified SigV4 computation: canonical request → string-to-sign → signing
|
|
45
|
+
* key → signature. Verified against AWS's official `get-vanilla` test vector.
|
|
46
|
+
*/
|
|
47
|
+
export declare function computeSigV4(input: CanonicalInput): SigV4Result;
|
|
48
|
+
/** Inputs for an R2/S3 request signature. */
|
|
49
|
+
export interface SignS3Input {
|
|
50
|
+
method: 'GET' | 'PUT' | 'DELETE';
|
|
51
|
+
accountId: string;
|
|
52
|
+
bucket: string;
|
|
53
|
+
/** Object key; omit for bucket-level operations (e.g. ListObjectsV2). */
|
|
54
|
+
key?: string;
|
|
55
|
+
/** Query params; `undefined` values are dropped before signing. */
|
|
56
|
+
query?: Record<string, string | undefined>;
|
|
57
|
+
region: string;
|
|
58
|
+
accessKeyId: string;
|
|
59
|
+
secretAccessKey: string;
|
|
60
|
+
/** Hex SHA-256 of the body, or EMPTY_SHA256 for body-less requests. */
|
|
61
|
+
payloadHash: string;
|
|
62
|
+
/** Extra headers to send + sign (content-type, cache-control, x-amz-meta-*). */
|
|
63
|
+
extraHeaders?: Record<string, string>;
|
|
64
|
+
now: Date;
|
|
65
|
+
}
|
|
66
|
+
export interface SignedS3Request {
|
|
67
|
+
url: string;
|
|
68
|
+
headers: Record<string, string>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Build a signed R2 request: a path-style URL + AWS4-HMAC-SHA256 Authorization
|
|
72
|
+
* header. The `host` header is signed but not returned — fetch/undici sets it
|
|
73
|
+
* from the URL (same value), and `Host` is a forbidden header to set manually.
|
|
74
|
+
*/
|
|
75
|
+
export declare function signS3Request(input: SignS3Input): SignedS3Request;
|
|
76
|
+
//# sourceMappingURL=_sigv4.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_sigv4.d.ts","sourceRoot":"","sources":["../../src/storage/_sigv4.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,iFAAiF;AACjF,eAAO,MAAM,YAAY,qEAAqE,CAAC;AAE/F,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAE3D;AAQD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GAAG,MAAM,CAaxE;AAQD,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,aAAa,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC1B,4EAA4E;IAC5E,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CAkC/D;AAED,6CAA6C;AAC7C,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,GAAG,EAAE,IAAI,CAAC;CACX;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,eAAe,CAmDjE"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Signature Version 4 signer for the Cloudflare R2 (S3-compatible) API.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the request signing that @aws-sdk/client-s3 used to do for us.
|
|
5
|
+
* Uses only node:crypto — no external dependencies, no regex.
|
|
6
|
+
*
|
|
7
|
+
* The core (`computeSigV4`) is verified byte-for-byte against the official AWS
|
|
8
|
+
* SigV4 test-suite `get-vanilla` vector in __tests__/_sigv4.test.ts.
|
|
9
|
+
*
|
|
10
|
+
* Server-only. Do NOT import from client-side code or edge runtime.
|
|
11
|
+
*/
|
|
12
|
+
import { createHash, createHmac } from 'node:crypto';
|
|
13
|
+
const ALGORITHM = 'AWS4-HMAC-SHA256';
|
|
14
|
+
const S3_SERVICE = 's3';
|
|
15
|
+
/** Hex SHA-256 of the empty string — the payload hash for body-less requests. */
|
|
16
|
+
export const EMPTY_SHA256 = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
|
|
17
|
+
/** Hex SHA-256 of arbitrary bytes (request payload hashing). */
|
|
18
|
+
export function sha256Hex(data) {
|
|
19
|
+
return createHash('sha256').update(data).digest('hex');
|
|
20
|
+
}
|
|
21
|
+
function hmac(key, data) {
|
|
22
|
+
return createHmac('sha256', key).update(data, 'utf8').digest();
|
|
23
|
+
}
|
|
24
|
+
const UNRESERVED = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~';
|
|
25
|
+
/**
|
|
26
|
+
* RFC 3986 percent-encoding per the AWS SigV4 spec. `encodeSlash` is false for
|
|
27
|
+
* path segments (slashes are separators) and true for query keys/values.
|
|
28
|
+
*/
|
|
29
|
+
export function awsUriEncode(input, encodeSlash) {
|
|
30
|
+
let out = '';
|
|
31
|
+
for (const byte of new TextEncoder().encode(input)) {
|
|
32
|
+
const ch = String.fromCharCode(byte);
|
|
33
|
+
if (UNRESERVED.includes(ch)) {
|
|
34
|
+
out += ch;
|
|
35
|
+
}
|
|
36
|
+
else if (ch === '/' && !encodeSlash) {
|
|
37
|
+
out += '/';
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
out += `%${byte.toString(16).toUpperCase().padStart(2, '0')}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
function toAmzDate(now) {
|
|
46
|
+
const iso = now.toISOString(); // e.g. 2026-05-18T10:00:00.000Z
|
|
47
|
+
const amzDate = `${iso.slice(0, 4)}${iso.slice(5, 7)}${iso.slice(8, 10)}T${iso.slice(11, 13)}${iso.slice(14, 16)}${iso.slice(17, 19)}Z`;
|
|
48
|
+
return { amzDate, dateStamp: amzDate.slice(0, 8) };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The verified SigV4 computation: canonical request → string-to-sign → signing
|
|
52
|
+
* key → signature. Verified against AWS's official `get-vanilla` test vector.
|
|
53
|
+
*/
|
|
54
|
+
export function computeSigV4(input) {
|
|
55
|
+
const lowered = new Map();
|
|
56
|
+
for (const [name, value] of Object.entries(input.headersToSign)) {
|
|
57
|
+
lowered.set(name.toLowerCase(), value.trim());
|
|
58
|
+
}
|
|
59
|
+
const names = [...lowered.keys()].sort();
|
|
60
|
+
const canonicalHeaders = names.map((n) => `${n}:${lowered.get(n)}\n`).join('');
|
|
61
|
+
const signedHeaders = names.join(';');
|
|
62
|
+
const canonicalQuery = [...input.query]
|
|
63
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
64
|
+
.map(([k, v]) => `${awsUriEncode(k, true)}=${awsUriEncode(v, true)}`)
|
|
65
|
+
.join('&');
|
|
66
|
+
const canonicalRequest = [
|
|
67
|
+
input.method,
|
|
68
|
+
input.canonicalPath,
|
|
69
|
+
canonicalQuery,
|
|
70
|
+
canonicalHeaders,
|
|
71
|
+
signedHeaders,
|
|
72
|
+
input.payloadHash,
|
|
73
|
+
].join('\n');
|
|
74
|
+
const dateStamp = input.amzDate.slice(0, 8);
|
|
75
|
+
const scope = `${dateStamp}/${input.region}/${input.service}/aws4_request`;
|
|
76
|
+
const stringToSign = [ALGORITHM, input.amzDate, scope, sha256Hex(canonicalRequest)].join('\n');
|
|
77
|
+
const kDate = hmac(`AWS4${input.secretAccessKey}`, dateStamp);
|
|
78
|
+
const kRegion = hmac(kDate, input.region);
|
|
79
|
+
const kService = hmac(kRegion, input.service);
|
|
80
|
+
const kSigning = hmac(kService, 'aws4_request');
|
|
81
|
+
const signature = createHmac('sha256', kSigning).update(stringToSign, 'utf8').digest('hex');
|
|
82
|
+
return { signature, signedHeaders, scope, canonicalQuery };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build a signed R2 request: a path-style URL + AWS4-HMAC-SHA256 Authorization
|
|
86
|
+
* header. The `host` header is signed but not returned — fetch/undici sets it
|
|
87
|
+
* from the URL (same value), and `Host` is a forbidden header to set manually.
|
|
88
|
+
*/
|
|
89
|
+
export function signS3Request(input) {
|
|
90
|
+
const host = `${input.accountId}.r2.cloudflarestorage.com`;
|
|
91
|
+
const { amzDate } = toAmzDate(input.now);
|
|
92
|
+
const encodedKey = (input.key ?? '')
|
|
93
|
+
.split('/')
|
|
94
|
+
.map((segment) => awsUriEncode(segment, false))
|
|
95
|
+
.join('/');
|
|
96
|
+
const canonicalPath = input.key !== undefined
|
|
97
|
+
? `/${awsUriEncode(input.bucket, false)}/${encodedKey}`
|
|
98
|
+
: `/${awsUriEncode(input.bucket, false)}`;
|
|
99
|
+
const query = [];
|
|
100
|
+
for (const [k, v] of Object.entries(input.query ?? {})) {
|
|
101
|
+
if (v !== undefined)
|
|
102
|
+
query.push([k, v]);
|
|
103
|
+
}
|
|
104
|
+
const headersToSign = {
|
|
105
|
+
host,
|
|
106
|
+
'x-amz-date': amzDate,
|
|
107
|
+
'x-amz-content-sha256': input.payloadHash,
|
|
108
|
+
};
|
|
109
|
+
for (const [k, v] of Object.entries(input.extraHeaders ?? {})) {
|
|
110
|
+
headersToSign[k.toLowerCase()] = v;
|
|
111
|
+
}
|
|
112
|
+
const { signature, signedHeaders, scope, canonicalQuery } = computeSigV4({
|
|
113
|
+
method: input.method,
|
|
114
|
+
canonicalPath,
|
|
115
|
+
query,
|
|
116
|
+
headersToSign,
|
|
117
|
+
payloadHash: input.payloadHash,
|
|
118
|
+
amzDate,
|
|
119
|
+
region: input.region,
|
|
120
|
+
service: S3_SERVICE,
|
|
121
|
+
accessKeyId: input.accessKeyId,
|
|
122
|
+
secretAccessKey: input.secretAccessKey,
|
|
123
|
+
});
|
|
124
|
+
const headers = {
|
|
125
|
+
'x-amz-date': amzDate,
|
|
126
|
+
'x-amz-content-sha256': input.payloadHash,
|
|
127
|
+
authorization: `${ALGORITHM} Credential=${input.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`,
|
|
128
|
+
};
|
|
129
|
+
for (const [k, v] of Object.entries(input.extraHeaders ?? {})) {
|
|
130
|
+
headers[k.toLowerCase()] = v;
|
|
131
|
+
}
|
|
132
|
+
const url = `https://${host}${canonicalPath}${canonicalQuery ? `?${canonicalQuery}` : ''}`;
|
|
133
|
+
return { url, headers };
|
|
134
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, dependency-free readers for the two S3/R2 XML responses we consume:
|
|
3
|
+
* ListObjectsV2 results and `<Error>` bodies. Replaces the XML codec that
|
|
4
|
+
* @aws-sdk/client-s3 used to run for us.
|
|
5
|
+
*
|
|
6
|
+
* No regex (M2): a tag's text can never contain a raw `<` (XML escapes it to
|
|
7
|
+
* `<`), so `indexOf`-based tag scanning is unambiguous.
|
|
8
|
+
*/
|
|
9
|
+
export interface ListedObject {
|
|
10
|
+
key: string;
|
|
11
|
+
size: number;
|
|
12
|
+
lastModified: Date;
|
|
13
|
+
}
|
|
14
|
+
export interface ParsedList {
|
|
15
|
+
objects: ListedObject[];
|
|
16
|
+
isTruncated: boolean;
|
|
17
|
+
nextContinuationToken?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Parse an S3 ListObjectsV2 XML response into the fields the R2 provider needs. */
|
|
20
|
+
export declare function parseListObjectsV2(xml: string): ParsedList;
|
|
21
|
+
/** Extract `<Code>`/`<Message>` from an S3 `<Error>` response for diagnostics. */
|
|
22
|
+
export declare function s3ErrorFields(xml: string): {
|
|
23
|
+
code?: string;
|
|
24
|
+
message?: string;
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=_xml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_xml.d.ts","sourceRoot":"","sources":["../../src/storage/_xml.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA6BH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CA6B1D;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAK9E"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, dependency-free readers for the two S3/R2 XML responses we consume:
|
|
3
|
+
* ListObjectsV2 results and `<Error>` bodies. Replaces the XML codec that
|
|
4
|
+
* @aws-sdk/client-s3 used to run for us.
|
|
5
|
+
*
|
|
6
|
+
* No regex (M2): a tag's text can never contain a raw `<` (XML escapes it to
|
|
7
|
+
* `<`), so `indexOf`-based tag scanning is unambiguous.
|
|
8
|
+
*/
|
|
9
|
+
/** First `<tag>…</tag>` text content at or after `from`, or undefined. */
|
|
10
|
+
function firstTagValue(xml, tag, from = 0) {
|
|
11
|
+
const open = `<${tag}>`;
|
|
12
|
+
const start = xml.indexOf(open, from);
|
|
13
|
+
if (start === -1)
|
|
14
|
+
return undefined;
|
|
15
|
+
const contentStart = start + open.length;
|
|
16
|
+
const end = xml.indexOf(`</${tag}>`, contentStart);
|
|
17
|
+
if (end === -1)
|
|
18
|
+
return undefined;
|
|
19
|
+
return xml.slice(contentStart, end);
|
|
20
|
+
}
|
|
21
|
+
/** Decode the five predefined XML entities (no regex). `&` is decoded last. */
|
|
22
|
+
function decodeXmlEntities(value) {
|
|
23
|
+
if (!value.includes('&'))
|
|
24
|
+
return value;
|
|
25
|
+
return value
|
|
26
|
+
.split('<')
|
|
27
|
+
.join('<')
|
|
28
|
+
.split('>')
|
|
29
|
+
.join('>')
|
|
30
|
+
.split('"')
|
|
31
|
+
.join('"')
|
|
32
|
+
.split(''')
|
|
33
|
+
.join("'")
|
|
34
|
+
.split('&')
|
|
35
|
+
.join('&');
|
|
36
|
+
}
|
|
37
|
+
/** Parse an S3 ListObjectsV2 XML response into the fields the R2 provider needs. */
|
|
38
|
+
export function parseListObjectsV2(xml) {
|
|
39
|
+
const objects = [];
|
|
40
|
+
const contentsOpen = '<Contents>';
|
|
41
|
+
const contentsClose = '</Contents>';
|
|
42
|
+
let cursor = 0;
|
|
43
|
+
for (;;) {
|
|
44
|
+
const start = xml.indexOf(contentsOpen, cursor);
|
|
45
|
+
if (start === -1)
|
|
46
|
+
break;
|
|
47
|
+
const end = xml.indexOf(contentsClose, start);
|
|
48
|
+
if (end === -1)
|
|
49
|
+
break;
|
|
50
|
+
const block = xml.slice(start + contentsOpen.length, end);
|
|
51
|
+
const key = firstTagValue(block, 'Key');
|
|
52
|
+
if (key !== undefined) {
|
|
53
|
+
const lastModified = firstTagValue(block, 'LastModified');
|
|
54
|
+
objects.push({
|
|
55
|
+
key: decodeXmlEntities(key),
|
|
56
|
+
size: Number(firstTagValue(block, 'Size') ?? '0') || 0,
|
|
57
|
+
lastModified: lastModified ? new Date(lastModified) : new Date(0),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
cursor = end + contentsClose.length;
|
|
61
|
+
}
|
|
62
|
+
const token = firstTagValue(xml, 'NextContinuationToken');
|
|
63
|
+
return {
|
|
64
|
+
objects,
|
|
65
|
+
isTruncated: firstTagValue(xml, 'IsTruncated') === 'true',
|
|
66
|
+
nextContinuationToken: token !== undefined ? decodeXmlEntities(token) : undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** Extract `<Code>`/`<Message>` from an S3 `<Error>` response for diagnostics. */
|
|
70
|
+
export function s3ErrorFields(xml) {
|
|
71
|
+
return {
|
|
72
|
+
code: firstTagValue(xml, 'Code'),
|
|
73
|
+
message: firstTagValue(xml, 'Message'),
|
|
74
|
+
};
|
|
75
|
+
}
|
package/dist/storage/r2.d.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cloudflare R2 implementation of StorageProvider.
|
|
3
3
|
*
|
|
4
|
-
* R2 is S3-compatible
|
|
5
|
-
*
|
|
4
|
+
* R2 is S3-compatible. This is a native client: AWS SigV4 request signing
|
|
5
|
+
* (./_sigv4.ts, verified against AWS's official test vector) over global
|
|
6
|
+
* `fetch`, plus a small XML reader for ListObjectsV2 (./_xml.ts). No SDK
|
|
7
|
+
* dependency — only node:crypto + fetch.
|
|
8
|
+
*
|
|
9
|
+
* Path-style addressing is used for the S3 API calls
|
|
10
|
+
* (https://<account>.r2.cloudflarestorage.com/<bucket>/<key>); this has no
|
|
11
|
+
* effect on the public object URLs, which are built from `publicBaseUrl`.
|
|
6
12
|
*
|
|
7
13
|
* GAP-208 Phase 2a (2026-05-18). Phase 1 (interface + types) shipped in #959.
|
|
14
|
+
* Native client (dropping @aws-sdk/client-s3) landed 2026-06-09.
|
|
8
15
|
*
|
|
9
16
|
* Server-only. Do NOT import from client-side code or edge runtime.
|
|
17
|
+
*
|
|
18
|
+
* Known limitation (unchanged from the SDK version): request bodies are
|
|
19
|
+
* buffered fully in memory (see toUint8Array) so the payload can be hashed for
|
|
20
|
+
* SigV4. Streaming uploads (STREAMING-AWS4-HMAC-SHA256-PAYLOAD) are a future
|
|
21
|
+
* enhancement; presigned/private URLs remain Phase 2b.
|
|
10
22
|
*/
|
|
11
23
|
import type { R2Config, StorageProvider } from './types.js';
|
|
12
24
|
export declare function createR2Provider(config: R2Config): StorageProvider;
|
package/dist/storage/r2.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"r2.d.ts","sourceRoot":"","sources":["../../src/storage/r2.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"r2.d.ts","sourceRoot":"","sources":["../../src/storage/r2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,OAAO,KAAK,EAMV,QAAQ,EACR,eAAe,EAChB,MAAM,YAAY,CAAC;AAkKpB,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,QAAQ,GAAG,eAAe,CAElE"}
|
package/dist/storage/r2.js
CHANGED
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cloudflare R2 implementation of StorageProvider.
|
|
3
3
|
*
|
|
4
|
-
* R2 is S3-compatible
|
|
5
|
-
*
|
|
4
|
+
* R2 is S3-compatible. This is a native client: AWS SigV4 request signing
|
|
5
|
+
* (./_sigv4.ts, verified against AWS's official test vector) over global
|
|
6
|
+
* `fetch`, plus a small XML reader for ListObjectsV2 (./_xml.ts). No SDK
|
|
7
|
+
* dependency — only node:crypto + fetch.
|
|
8
|
+
*
|
|
9
|
+
* Path-style addressing is used for the S3 API calls
|
|
10
|
+
* (https://<account>.r2.cloudflarestorage.com/<bucket>/<key>); this has no
|
|
11
|
+
* effect on the public object URLs, which are built from `publicBaseUrl`.
|
|
6
12
|
*
|
|
7
13
|
* GAP-208 Phase 2a (2026-05-18). Phase 1 (interface + types) shipped in #959.
|
|
14
|
+
* Native client (dropping @aws-sdk/client-s3) landed 2026-06-09.
|
|
8
15
|
*
|
|
9
16
|
* Server-only. Do NOT import from client-side code or edge runtime.
|
|
17
|
+
*
|
|
18
|
+
* Known limitation (unchanged from the SDK version): request bodies are
|
|
19
|
+
* buffered fully in memory (see toUint8Array) so the payload can be hashed for
|
|
20
|
+
* SigV4. Streaming uploads (STREAMING-AWS4-HMAC-SHA256-PAYLOAD) are a future
|
|
21
|
+
* enhancement; presigned/private URLs remain Phase 2b.
|
|
10
22
|
*/
|
|
11
|
-
import { DeleteObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client, } from '@aws-sdk/client-s3';
|
|
12
23
|
import { toUint8Array } from './_helpers.js';
|
|
24
|
+
import { EMPTY_SHA256, sha256Hex, signS3Request } from './_sigv4.js';
|
|
25
|
+
import { parseListObjectsV2, s3ErrorFields } from './_xml.js';
|
|
13
26
|
const PROVIDER_TAG = 'r2';
|
|
14
27
|
const DEFAULT_CONTENT_TYPE = 'application/octet-stream';
|
|
15
28
|
const DEFAULT_LIST_LIMIT = 1000;
|
|
29
|
+
const REGION = 'auto';
|
|
16
30
|
class R2Provider {
|
|
17
31
|
provider = PROVIDER_TAG;
|
|
18
|
-
|
|
32
|
+
accountId;
|
|
33
|
+
accessKeyId;
|
|
34
|
+
secretAccessKey;
|
|
19
35
|
bucket;
|
|
20
36
|
publicBaseUrl;
|
|
21
37
|
constructor(config) {
|
|
@@ -25,16 +41,11 @@ class R2Provider {
|
|
|
25
41
|
"custom domain (e.g. 'https://media.revealui.com') or the R2 dev URL " +
|
|
26
42
|
"('https://<account-id>.r2.cloudflarestorage.com/<bucket>').");
|
|
27
43
|
}
|
|
44
|
+
this.accountId = config.accountId;
|
|
45
|
+
this.accessKeyId = config.accessKeyId;
|
|
46
|
+
this.secretAccessKey = config.secretAccessKey;
|
|
28
47
|
this.bucket = config.bucket;
|
|
29
48
|
this.publicBaseUrl = stripTrailingSlash(config.publicBaseUrl);
|
|
30
|
-
this.client = new S3Client({
|
|
31
|
-
region: 'auto',
|
|
32
|
-
endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,
|
|
33
|
-
credentials: {
|
|
34
|
-
accessKeyId: config.accessKeyId,
|
|
35
|
-
secretAccessKey: config.secretAccessKey,
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
49
|
}
|
|
39
50
|
async put(key, data, opts) {
|
|
40
51
|
if (opts?.access === 'private') {
|
|
@@ -43,15 +54,40 @@ class R2Provider {
|
|
|
43
54
|
'lands in Phase 2b.');
|
|
44
55
|
}
|
|
45
56
|
const body = await toUint8Array(data);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
57
|
+
const extraHeaders = {
|
|
58
|
+
'content-type': opts?.contentType ?? DEFAULT_CONTENT_TYPE,
|
|
59
|
+
};
|
|
60
|
+
if (opts?.cacheControl) {
|
|
61
|
+
extraHeaders['cache-control'] = opts.cacheControl;
|
|
62
|
+
}
|
|
63
|
+
for (const [name, value] of Object.entries(opts?.metadata ?? {})) {
|
|
64
|
+
extraHeaders[`x-amz-meta-${name.toLowerCase()}`] = value;
|
|
65
|
+
}
|
|
66
|
+
const { url, headers } = signS3Request({
|
|
67
|
+
method: 'PUT',
|
|
68
|
+
accountId: this.accountId,
|
|
69
|
+
bucket: this.bucket,
|
|
70
|
+
key,
|
|
71
|
+
region: REGION,
|
|
72
|
+
accessKeyId: this.accessKeyId,
|
|
73
|
+
secretAccessKey: this.secretAccessKey,
|
|
74
|
+
payloadHash: sha256Hex(body),
|
|
75
|
+
extraHeaders,
|
|
76
|
+
now: new Date(),
|
|
77
|
+
});
|
|
78
|
+
// No manual content-length: it is a forbidden fetch header that undici
|
|
79
|
+
// computes from the body itself, and setting it throws UND_ERR_INVALID_ARG
|
|
80
|
+
// ("invalid content-length header") on the R2 PUT. `headers` already carries
|
|
81
|
+
// the signed set. `as BodyInit` bridges the TS lib's ArrayBufferLike vs
|
|
82
|
+
// ArrayBuffer typed-array variance for fetch bodies (cf. src/api/compression.ts).
|
|
83
|
+
const response = await fetch(url, {
|
|
84
|
+
method: 'PUT',
|
|
85
|
+
headers,
|
|
86
|
+
body: body,
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
throw await storageError('PUT', key, response);
|
|
90
|
+
}
|
|
55
91
|
return {
|
|
56
92
|
key,
|
|
57
93
|
url: `${this.publicBaseUrl}/${key}`,
|
|
@@ -61,34 +97,56 @@ class R2Provider {
|
|
|
61
97
|
}
|
|
62
98
|
async del(keyOrUrl) {
|
|
63
99
|
const key = this.extractKey(keyOrUrl);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
100
|
+
const { url, headers } = signS3Request({
|
|
101
|
+
method: 'DELETE',
|
|
102
|
+
accountId: this.accountId,
|
|
103
|
+
bucket: this.bucket,
|
|
104
|
+
key,
|
|
105
|
+
region: REGION,
|
|
106
|
+
accessKeyId: this.accessKeyId,
|
|
107
|
+
secretAccessKey: this.secretAccessKey,
|
|
108
|
+
payloadHash: EMPTY_SHA256,
|
|
109
|
+
now: new Date(),
|
|
110
|
+
});
|
|
111
|
+
// Best-effort: a missing key is not an error. S3/R2 returns 204 regardless;
|
|
112
|
+
// tolerate 404 defensively.
|
|
113
|
+
const response = await fetch(url, { method: 'DELETE', headers });
|
|
114
|
+
if (!response.ok && response.status !== 404) {
|
|
115
|
+
throw await storageError('DELETE', key, response);
|
|
116
|
+
}
|
|
68
117
|
}
|
|
69
118
|
async list(opts) {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
},
|
|
86
|
-
];
|
|
119
|
+
const { url, headers } = signS3Request({
|
|
120
|
+
method: 'GET',
|
|
121
|
+
accountId: this.accountId,
|
|
122
|
+
bucket: this.bucket,
|
|
123
|
+
region: REGION,
|
|
124
|
+
accessKeyId: this.accessKeyId,
|
|
125
|
+
secretAccessKey: this.secretAccessKey,
|
|
126
|
+
payloadHash: EMPTY_SHA256,
|
|
127
|
+
query: {
|
|
128
|
+
'list-type': '2',
|
|
129
|
+
prefix: opts?.prefix,
|
|
130
|
+
'max-keys': String(opts?.limit ?? DEFAULT_LIST_LIMIT),
|
|
131
|
+
'continuation-token': opts?.cursor,
|
|
132
|
+
},
|
|
133
|
+
now: new Date(),
|
|
87
134
|
});
|
|
135
|
+
const response = await fetch(url, { method: 'GET', headers });
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
throw await storageError('LIST', this.bucket, response);
|
|
138
|
+
}
|
|
139
|
+
const parsed = parseListObjectsV2(await response.text());
|
|
140
|
+
const items = parsed.objects.map((entry) => ({
|
|
141
|
+
key: entry.key,
|
|
142
|
+
url: `${this.publicBaseUrl}/${entry.key}`,
|
|
143
|
+
size: entry.size,
|
|
144
|
+
uploadedAt: entry.lastModified,
|
|
145
|
+
}));
|
|
88
146
|
return {
|
|
89
147
|
items,
|
|
90
|
-
cursor:
|
|
91
|
-
hasMore:
|
|
148
|
+
cursor: parsed.nextContinuationToken,
|
|
149
|
+
hasMore: parsed.isTruncated,
|
|
92
150
|
};
|
|
93
151
|
}
|
|
94
152
|
extractKey(keyOrUrl) {
|
|
@@ -104,6 +162,11 @@ export function createR2Provider(config) {
|
|
|
104
162
|
return new R2Provider(config);
|
|
105
163
|
}
|
|
106
164
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
165
|
+
async function storageError(operation, key, response) {
|
|
166
|
+
const body = await response.text().catch(() => '');
|
|
167
|
+
const { code, message } = s3ErrorFields(body);
|
|
168
|
+
return new Error(`R2 ${operation} failed for "${key}": ${code ?? response.status} ${message ?? response.statusText}`);
|
|
169
|
+
}
|
|
107
170
|
function tryParseUrl(value) {
|
|
108
171
|
try {
|
|
109
172
|
return new URL(value);
|
package/package.json
CHANGED
|
@@ -1,45 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revealui/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "RevealUI's runtime engine — admin UI, REST API, auth, rich text, plugin system, and access control. The heart of the open-source platform.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@
|
|
8
|
-
"@
|
|
9
|
-
"@lexical/
|
|
10
|
-
"@lexical/
|
|
11
|
-
"@lexical/
|
|
12
|
-
"@lexical/
|
|
13
|
-
"@lexical/
|
|
14
|
-
"@lexical/
|
|
15
|
-
"@lexical/
|
|
16
|
-
"@lexical/
|
|
17
|
-
"@lexical/
|
|
18
|
-
"@lexical/
|
|
19
|
-
"@lexical/yjs": "^0.44.0",
|
|
7
|
+
"@electric-sql/pglite": "^0.5.1",
|
|
8
|
+
"@lexical/clipboard": "^0.45.0",
|
|
9
|
+
"@lexical/code": "^0.45.0",
|
|
10
|
+
"@lexical/html": "^0.45.0",
|
|
11
|
+
"@lexical/link": "^0.45.0",
|
|
12
|
+
"@lexical/list": "^0.45.0",
|
|
13
|
+
"@lexical/react": "^0.45.0",
|
|
14
|
+
"@lexical/rich-text": "^0.45.0",
|
|
15
|
+
"@lexical/selection": "^0.45.0",
|
|
16
|
+
"@lexical/table": "^0.45.0",
|
|
17
|
+
"@lexical/utils": "^0.45.0",
|
|
18
|
+
"@lexical/yjs": "^0.45.0",
|
|
20
19
|
"@vercel/blob": "^2.3.3",
|
|
21
20
|
"bcryptjs": "^3.0.3",
|
|
22
21
|
"dataloader": "^2.2.3",
|
|
23
|
-
"jose": "^
|
|
24
|
-
"lexical": "^0.
|
|
22
|
+
"jose": "^5.10.0",
|
|
23
|
+
"lexical": "^0.45.0",
|
|
25
24
|
"pg": "^8.21.0",
|
|
26
|
-
"yjs": "^13.6.
|
|
25
|
+
"yjs": "^13.6.31",
|
|
27
26
|
"zod": "^4.4.3",
|
|
28
|
-
"@revealui/cache": "0.2.
|
|
29
|
-
"@revealui/contracts": "0.6.
|
|
27
|
+
"@revealui/cache": "0.2.2",
|
|
28
|
+
"@revealui/contracts": "0.6.1",
|
|
30
29
|
"@revealui/resilience": "0.2.4",
|
|
31
|
-
"@revealui/security": "0.4.
|
|
30
|
+
"@revealui/security": "0.4.1",
|
|
32
31
|
"@revealui/utils": "0.3.5"
|
|
33
32
|
},
|
|
34
33
|
"devDependencies": {
|
|
35
34
|
"@types/json-schema": "^7.0.15",
|
|
36
35
|
"@types/pg": "^8.20.0",
|
|
37
|
-
"@types/react": "^19.2.
|
|
36
|
+
"@types/react": "^19.2.17",
|
|
38
37
|
"@types/react-dom": "^19.2.3",
|
|
39
|
-
"@vitest/coverage-v8": "^4.1.
|
|
38
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
40
39
|
"expect-type": "^1.3.0",
|
|
41
40
|
"tsup": "^8.5.1",
|
|
42
|
-
"vitest": "^4.1.
|
|
41
|
+
"vitest": "^4.1.8",
|
|
43
42
|
"@revealui/dev": "0.1.0"
|
|
44
43
|
},
|
|
45
44
|
"engines": {
|