@highlightxyz/sdk 0.2.0 → 0.4.0
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/lib/client.d.ts +7 -0
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +9 -1
- package/lib/v1/gen/sdk.gen.d.ts +172 -71
- package/lib/v1/gen/sdk.gen.d.ts.map +1 -1
- package/lib/v1/gen/sdk.gen.js +289 -88
- package/lib/v1/gen/types.gen.d.ts +943 -325
- package/lib/v1/gen/types.gen.d.ts.map +1 -1
- package/lib/v1/gen/types.gen.js +18 -13
- package/lib/v1/media.d.ts +137 -1
- package/lib/v1/media.d.ts.map +1 -1
- package/lib/v1/media.js +243 -9
- package/package.json +4 -1
package/lib/v1/media.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/v1/media.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/v1/media.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,oBAAoB,EAIzB,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAE5B,eAAO,MAAM,mCAAmC,MAAM,CAAC;AACvD,eAAO,MAAM,2BAA2B,WAAa,CAAC;AAEtD,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;AAKrF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;6CAkC9B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,OAAO,GAAG,YAAY,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6D+B,CAAC;AAEjE,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,oBAAoB,CAAC;AAElE,eAAO,MAAM,+BAA+B,+CAKK,CAAC;AAclD,eAAO,MAAM,uBAAuB,+CAGnC,CAAC;AAEF,eAAO,MAAM,0BAA0B,uDAItC,CAAC;AAEF,eAAO,MAAM,oBAAoB,oHAOhC,CAAC;AAEF,eAAO,MAAM,4BAA4B,iLAQxC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,wDAmBvC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,yBAAyB;;yCAgBrC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,4BAA4B,EAAE,kBAM1C,CAAC;AAIF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,QAAQ,6KA2BpB,CAAC;AAwBF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe;;;+BAY3B,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB;;;;+BA0B5B,CAAC"}
|
package/lib/v1/media.js
CHANGED
|
@@ -1,11 +1,241 @@
|
|
|
1
1
|
// Hand-written media URL helpers (not generated — do not place under ./gen).
|
|
2
2
|
//
|
|
3
|
-
// The
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
3
|
+
// The API returns storage locations as `provider` + `reference`; turning those
|
|
4
|
+
// into a URL is the consumer's job, and this is where it happens. A location's
|
|
5
|
+
// URL is composed from the asset's own identity, so the backend's storage key
|
|
6
|
+
// layout is invisible here and assets written under older layouts resolve
|
|
7
|
+
// identically.
|
|
8
|
+
//
|
|
9
|
+
// These helpers answer "what should the client fetch right now" — Origin-first,
|
|
10
|
+
// with the caller free to layer gateway failover on top. The backend's
|
|
11
|
+
// `artifactUri` answers a different question ("what immutable URI do we bake
|
|
12
|
+
// into an on-chain document") and is deliberately not shared with this path.
|
|
13
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
14
|
+
import { bytesToHex } from "@noble/hashes/utils";
|
|
15
|
+
import { LocationStatus, MediaKind, StorageProvider, StorageRole, } from "./gen/types.gen.js";
|
|
16
|
+
export const MEDIA_IMAGE_VARIANT_QUERY_PARAMETER = "v";
|
|
17
|
+
export const MEDIA_IMAGE_MAX_INPUT_BYTES = 20_000_000;
|
|
18
|
+
const isReadableStream = (source) => "getReader" in source;
|
|
19
|
+
/**
|
|
20
|
+
* Compute the upload contract's canonical SHA-256 without collecting the
|
|
21
|
+
* complete file in memory. Browser callers pass `File.stream()`; Node callers
|
|
22
|
+
* can pass `createReadStream(path)` directly.
|
|
23
|
+
*/
|
|
24
|
+
export const sha256MediaContent = async (source, options) => {
|
|
25
|
+
const hasher = sha256.create();
|
|
26
|
+
let bytesHashed = 0;
|
|
27
|
+
const update = (chunk) => {
|
|
28
|
+
if (options?.signal?.aborted) {
|
|
29
|
+
throw new DOMException("Hashing aborted.", "AbortError");
|
|
30
|
+
}
|
|
31
|
+
hasher.update(chunk);
|
|
32
|
+
bytesHashed += chunk.byteLength;
|
|
33
|
+
options?.onProgress?.(bytesHashed);
|
|
34
|
+
};
|
|
35
|
+
if (isReadableStream(source)) {
|
|
36
|
+
const reader = source.getReader();
|
|
37
|
+
try {
|
|
38
|
+
while (true) {
|
|
39
|
+
const result = await reader.read();
|
|
40
|
+
if (result.done)
|
|
41
|
+
break;
|
|
42
|
+
update(result.value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
reader.releaseLock();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
for await (const chunk of source)
|
|
51
|
+
update(chunk);
|
|
52
|
+
}
|
|
53
|
+
return `sha256:${bytesToHex(hasher.digest())}`;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Public, versioned image transformation contract shared by the delivery
|
|
57
|
+
* Worker and SDK consumers. Existing query values and dimensions are immutable;
|
|
58
|
+
* a changed transform must use a new query value.
|
|
59
|
+
*/
|
|
60
|
+
export const MEDIA_IMAGE_VARIANTS = {
|
|
61
|
+
avatar: {
|
|
62
|
+
queryValue: "avatar-v1",
|
|
63
|
+
width: 256,
|
|
64
|
+
height: 256,
|
|
65
|
+
fit: "cover",
|
|
66
|
+
quality: 75,
|
|
67
|
+
responsive: false,
|
|
68
|
+
},
|
|
69
|
+
thumb: {
|
|
70
|
+
queryValue: "thumb-v1",
|
|
71
|
+
width: 240,
|
|
72
|
+
height: 240,
|
|
73
|
+
fit: "cover",
|
|
74
|
+
quality: 75,
|
|
75
|
+
responsive: false,
|
|
76
|
+
},
|
|
77
|
+
small: {
|
|
78
|
+
queryValue: "small-v1",
|
|
79
|
+
width: 320,
|
|
80
|
+
fit: "scale-down",
|
|
81
|
+
quality: 78,
|
|
82
|
+
responsive: true,
|
|
83
|
+
},
|
|
84
|
+
card: {
|
|
85
|
+
queryValue: "card-v1",
|
|
86
|
+
width: 640,
|
|
87
|
+
height: 640,
|
|
88
|
+
fit: "cover",
|
|
89
|
+
quality: 80,
|
|
90
|
+
responsive: false,
|
|
91
|
+
},
|
|
92
|
+
preview: {
|
|
93
|
+
queryValue: "preview-v1",
|
|
94
|
+
width: 960,
|
|
95
|
+
fit: "scale-down",
|
|
96
|
+
quality: 82,
|
|
97
|
+
responsive: true,
|
|
98
|
+
},
|
|
99
|
+
hero: {
|
|
100
|
+
queryValue: "hero-v1",
|
|
101
|
+
width: 1600,
|
|
102
|
+
fit: "scale-down",
|
|
103
|
+
quality: 85,
|
|
104
|
+
responsive: true,
|
|
105
|
+
},
|
|
106
|
+
og: {
|
|
107
|
+
queryValue: "og-v1",
|
|
108
|
+
width: 1200,
|
|
109
|
+
height: 630,
|
|
110
|
+
fit: "cover",
|
|
111
|
+
quality: 85,
|
|
112
|
+
responsive: false,
|
|
113
|
+
},
|
|
114
|
+
full: {
|
|
115
|
+
queryValue: "full-v1",
|
|
116
|
+
width: 4096,
|
|
117
|
+
fit: "scale-down",
|
|
118
|
+
quality: 90,
|
|
119
|
+
responsive: true,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
export const RESPONSIVE_MEDIA_IMAGE_VARIANTS = [
|
|
123
|
+
"small",
|
|
124
|
+
"preview",
|
|
125
|
+
"hero",
|
|
126
|
+
"full",
|
|
127
|
+
];
|
|
128
|
+
const SUPPORTED_IMAGE_CONTENT_TYPES = new Set([
|
|
129
|
+
"image/avif",
|
|
130
|
+
"image/gif",
|
|
131
|
+
"image/heic",
|
|
132
|
+
"image/heif",
|
|
133
|
+
"image/jpeg",
|
|
134
|
+
"image/jpg",
|
|
135
|
+
"image/png",
|
|
136
|
+
"image/webp",
|
|
137
|
+
]);
|
|
138
|
+
const MODERN_ASSET_PATH = /^\/[0-9a-fA-F-]+(?:\/.*)?$/;
|
|
139
|
+
export const isMediaImageContentType = (value) => {
|
|
140
|
+
const contentType = value?.split(";")[0]?.trim().toLowerCase();
|
|
141
|
+
return contentType !== undefined && SUPPORTED_IMAGE_CONTENT_TYPES.has(contentType);
|
|
142
|
+
};
|
|
143
|
+
export const mediaImageVariantFromQuery = (value) => {
|
|
144
|
+
return (Object.values(MEDIA_IMAGE_VARIANTS).find((variant) => variant.queryValue === value) ?? null);
|
|
145
|
+
};
|
|
146
|
+
export const mediaImageVariantUrl = (sourceUrl, variant) => {
|
|
147
|
+
const url = new URL(sourceUrl);
|
|
148
|
+
url.searchParams.set(MEDIA_IMAGE_VARIANT_QUERY_PARAMETER, MEDIA_IMAGE_VARIANTS[variant].queryValue);
|
|
149
|
+
return url.toString();
|
|
150
|
+
};
|
|
151
|
+
export const responsiveMediaImageVariants = (target) => {
|
|
152
|
+
const targetDefinition = MEDIA_IMAGE_VARIANTS[target];
|
|
153
|
+
if (!targetDefinition.responsive)
|
|
154
|
+
return [];
|
|
155
|
+
return RESPONSIVE_MEDIA_IMAGE_VARIANTS.filter((variant) => MEDIA_IMAGE_VARIANTS[variant].width <= targetDefinition.width);
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* True only for public URLs owned by Highlight's image-capable delivery
|
|
159
|
+
* Worker. The localhost forms mirror the Worker's explicit development
|
|
160
|
+
* namespaces.
|
|
161
|
+
*/
|
|
162
|
+
export const isHighlightMediaDeliveryUrl = (value, hosts = DEFAULT_MEDIA_DELIVERY_HOSTS) => {
|
|
163
|
+
let url;
|
|
164
|
+
try {
|
|
165
|
+
url = new URL(value);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
171
|
+
return false;
|
|
172
|
+
if (value.startsWith(`${hosts.assets}/`)) {
|
|
173
|
+
return MODERN_ASSET_PATH.test(value.slice(hosts.assets.length));
|
|
174
|
+
}
|
|
175
|
+
if (value.startsWith(`${hosts.legacyAssets}/`)) {
|
|
176
|
+
return value.slice(hosts.legacyAssets.length).startsWith("/main/");
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Client-side preflight for requesting a named image variant. A zero file size
|
|
182
|
+
* is currently the legacy importer's "unknown" sentinel; the delivery Worker
|
|
183
|
+
* still validates the actual R2 Content-Length before invoking Images.
|
|
184
|
+
*/
|
|
185
|
+
export const isMediaImageTransformable = (media, source, hosts = DEFAULT_MEDIA_DELIVERY_HOSTS) => {
|
|
186
|
+
const sizeIsEligible = media.fileSize === null ||
|
|
187
|
+
media.fileSize === 0 ||
|
|
188
|
+
media.fileSize <= MEDIA_IMAGE_MAX_INPUT_BYTES;
|
|
189
|
+
return (source.provider === StorageProvider.R2 &&
|
|
190
|
+
media.kind === MediaKind.File &&
|
|
191
|
+
isMediaImageContentType(media.mimeType) &&
|
|
192
|
+
sizeIsEligible &&
|
|
193
|
+
isHighlightMediaDeliveryUrl(source.url, hosts));
|
|
194
|
+
};
|
|
195
|
+
export const DEFAULT_MEDIA_DELIVERY_HOSTS = {
|
|
196
|
+
assets: "https://assets.highlight.xyz",
|
|
197
|
+
metadata: "https://metadata.highlight.xyz",
|
|
198
|
+
legacyAssets: "https://highlight-creator-assets.highlight.xyz",
|
|
199
|
+
arweaveGateway: "https://arweave.net",
|
|
200
|
+
ipfsGateway: "https://ipfs.io",
|
|
201
|
+
};
|
|
202
|
+
const LEGACY_REFERENCE = /^main\//;
|
|
203
|
+
/**
|
|
204
|
+
* The URL to fetch a media location from.
|
|
205
|
+
*
|
|
206
|
+
* R2 URLs are composed from the asset's own identity — a File is `/{mediaId}`,
|
|
207
|
+
* a directory child is `/{parentId}/{path}` — never by parsing the storage key.
|
|
208
|
+
* That is why a stored key can change shape without any URL changing, and why
|
|
209
|
+
* assets written under older key layouts resolve identically.
|
|
210
|
+
*
|
|
211
|
+
* This answers "what should the browser fetch right now". It is deliberately
|
|
212
|
+
* not the backend's `artifactUri`, which answers "what immutable URI do we bake
|
|
213
|
+
* into an on-chain document" — Archive-first and permanent.
|
|
214
|
+
*/
|
|
215
|
+
export const mediaUrl = (media, location, hosts = DEFAULT_MEDIA_DELIVERY_HOSTS) => {
|
|
216
|
+
switch (location.provider) {
|
|
217
|
+
case StorageProvider.R2: {
|
|
218
|
+
// Legacy keys are the one case where the key *is* the public path.
|
|
219
|
+
if (LEGACY_REFERENCE.test(location.reference)) {
|
|
220
|
+
return `${hosts.legacyAssets}/${location.reference}`;
|
|
221
|
+
}
|
|
222
|
+
if (media.parentId && media.path) {
|
|
223
|
+
return `${hosts.assets}/${media.parentId}/${media.path}`;
|
|
224
|
+
}
|
|
225
|
+
return media.kind === MediaKind.Directory
|
|
226
|
+
? `${hosts.assets}/${media.id}/`
|
|
227
|
+
: `${hosts.assets}/${media.id}`;
|
|
228
|
+
}
|
|
229
|
+
case StorageProvider.Arweave:
|
|
230
|
+
return `${hosts.arweaveGateway}/${location.reference}`;
|
|
231
|
+
case StorageProvider.Ipfs:
|
|
232
|
+
return `${hosts.ipfsGateway}/ipfs/${location.reference.replace(/^ipfs:\/\//, "").replace(/^\//, "")}`;
|
|
233
|
+
case StorageProvider.External:
|
|
234
|
+
return location.reference;
|
|
235
|
+
default:
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
9
239
|
const DEFAULT_PREFERENCE = [
|
|
10
240
|
StorageRole.Archive,
|
|
11
241
|
StorageRole.Origin,
|
|
@@ -15,7 +245,7 @@ const DEFAULT_PREFERENCE = [
|
|
|
15
245
|
const DEFAULT_BUNDLE_ENTRY = "index.html";
|
|
16
246
|
const pickLocation = (media, preference) => {
|
|
17
247
|
for (const role of preference) {
|
|
18
|
-
const location = media.locations.find((l) => l.role === role && l.status === LocationStatus.Succeeded
|
|
248
|
+
const location = media.locations.find((l) => l.role === role && l.status === LocationStatus.Succeeded);
|
|
19
249
|
if (location)
|
|
20
250
|
return location;
|
|
21
251
|
}
|
|
@@ -37,7 +267,8 @@ export const resolveMediaUrl = (media, options) => {
|
|
|
37
267
|
? options.prefer
|
|
38
268
|
: [options.prefer]
|
|
39
269
|
: DEFAULT_PREFERENCE;
|
|
40
|
-
|
|
270
|
+
const location = pickLocation(media, preference);
|
|
271
|
+
return location ? mediaUrl(media, location, options?.hosts) : null;
|
|
41
272
|
};
|
|
42
273
|
/**
|
|
43
274
|
* Resolve the renderable URL for a generative code bundle (a Directory media
|
|
@@ -54,7 +285,10 @@ export const resolveMediaUrl = (media, options) => {
|
|
|
54
285
|
* meaningful.
|
|
55
286
|
*/
|
|
56
287
|
export const resolveRenderUrl = (media, options) => {
|
|
57
|
-
const base = resolveMediaUrl(media, {
|
|
288
|
+
const base = resolveMediaUrl(media, {
|
|
289
|
+
prefer: [StorageRole.Origin, StorageRole.Mirror],
|
|
290
|
+
hosts: options?.hosts,
|
|
291
|
+
});
|
|
58
292
|
if (!base)
|
|
59
293
|
return null;
|
|
60
294
|
// Resolve the entry as a child of the directory base. `new URL(entry, base)`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@highlightxyz/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Official TypeScript SDK for the Highlight API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ethereum",
|
|
@@ -39,6 +39,9 @@
|
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@noble/hashes": "^1.8.0"
|
|
44
|
+
},
|
|
42
45
|
"main": "./lib/index.js",
|
|
43
46
|
"module": "./lib/index.js",
|
|
44
47
|
"types": "./lib/index.d.ts"
|