@highlightxyz/sdk 0.3.0 → 0.5.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/v1/gen/sdk.gen.d.ts +105 -72
- package/lib/v1/gen/sdk.gen.d.ts.map +1 -1
- package/lib/v1/gen/sdk.gen.js +195 -106
- package/lib/v1/gen/types.gen.d.ts +517 -250
- 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
CHANGED
|
@@ -1,4 +1,138 @@
|
|
|
1
|
-
import { type MediaAsset, StorageRole } from "./gen/types.gen.js";
|
|
1
|
+
import { type MediaAsset, type MediaStorageLocation, StorageRole } from "./gen/types.gen.js";
|
|
2
|
+
export declare const MEDIA_IMAGE_VARIANT_QUERY_PARAMETER = "v";
|
|
3
|
+
export declare const MEDIA_IMAGE_MAX_INPUT_BYTES = 20000000;
|
|
4
|
+
export type MediaHashSource = ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;
|
|
5
|
+
/**
|
|
6
|
+
* Compute the upload contract's canonical SHA-256 without collecting the
|
|
7
|
+
* complete file in memory. Browser callers pass `File.stream()`; Node callers
|
|
8
|
+
* can pass `createReadStream(path)` directly.
|
|
9
|
+
*/
|
|
10
|
+
export declare const sha256MediaContent: (source: MediaHashSource, options?: {
|
|
11
|
+
signal?: AbortSignal | undefined;
|
|
12
|
+
onProgress?: ((bytesHashed: number) => void) | undefined;
|
|
13
|
+
} | undefined) => Promise<`sha256:${string}`>;
|
|
14
|
+
export type MediaImageVariantDefinition = {
|
|
15
|
+
queryValue: string;
|
|
16
|
+
width: number;
|
|
17
|
+
height?: number;
|
|
18
|
+
fit: "cover" | "scale-down";
|
|
19
|
+
quality: number;
|
|
20
|
+
responsive: boolean;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Public, versioned image transformation contract shared by the delivery
|
|
24
|
+
* Worker and SDK consumers. Existing query values and dimensions are immutable;
|
|
25
|
+
* a changed transform must use a new query value.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MEDIA_IMAGE_VARIANTS: {
|
|
28
|
+
readonly avatar: {
|
|
29
|
+
readonly queryValue: "avatar-v1";
|
|
30
|
+
readonly width: 256;
|
|
31
|
+
readonly height: 256;
|
|
32
|
+
readonly fit: "cover";
|
|
33
|
+
readonly quality: 75;
|
|
34
|
+
readonly responsive: false;
|
|
35
|
+
};
|
|
36
|
+
readonly thumb: {
|
|
37
|
+
readonly queryValue: "thumb-v1";
|
|
38
|
+
readonly width: 240;
|
|
39
|
+
readonly height: 240;
|
|
40
|
+
readonly fit: "cover";
|
|
41
|
+
readonly quality: 75;
|
|
42
|
+
readonly responsive: false;
|
|
43
|
+
};
|
|
44
|
+
readonly small: {
|
|
45
|
+
readonly queryValue: "small-v1";
|
|
46
|
+
readonly width: 320;
|
|
47
|
+
readonly fit: "scale-down";
|
|
48
|
+
readonly quality: 78;
|
|
49
|
+
readonly responsive: true;
|
|
50
|
+
};
|
|
51
|
+
readonly card: {
|
|
52
|
+
readonly queryValue: "card-v1";
|
|
53
|
+
readonly width: 640;
|
|
54
|
+
readonly height: 640;
|
|
55
|
+
readonly fit: "cover";
|
|
56
|
+
readonly quality: 80;
|
|
57
|
+
readonly responsive: false;
|
|
58
|
+
};
|
|
59
|
+
readonly preview: {
|
|
60
|
+
readonly queryValue: "preview-v1";
|
|
61
|
+
readonly width: 960;
|
|
62
|
+
readonly fit: "scale-down";
|
|
63
|
+
readonly quality: 82;
|
|
64
|
+
readonly responsive: true;
|
|
65
|
+
};
|
|
66
|
+
readonly hero: {
|
|
67
|
+
readonly queryValue: "hero-v1";
|
|
68
|
+
readonly width: 1600;
|
|
69
|
+
readonly fit: "scale-down";
|
|
70
|
+
readonly quality: 85;
|
|
71
|
+
readonly responsive: true;
|
|
72
|
+
};
|
|
73
|
+
readonly og: {
|
|
74
|
+
readonly queryValue: "og-v1";
|
|
75
|
+
readonly width: 1200;
|
|
76
|
+
readonly height: 630;
|
|
77
|
+
readonly fit: "cover";
|
|
78
|
+
readonly quality: 85;
|
|
79
|
+
readonly responsive: false;
|
|
80
|
+
};
|
|
81
|
+
readonly full: {
|
|
82
|
+
readonly queryValue: "full-v1";
|
|
83
|
+
readonly width: 4096;
|
|
84
|
+
readonly fit: "scale-down";
|
|
85
|
+
readonly quality: 90;
|
|
86
|
+
readonly responsive: true;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
export type MediaImageVariant = keyof typeof MEDIA_IMAGE_VARIANTS;
|
|
90
|
+
export declare const RESPONSIVE_MEDIA_IMAGE_VARIANTS: readonly ["small", "preview", "hero", "full"];
|
|
91
|
+
export declare const isMediaImageContentType: (value: string | null | undefined) => boolean;
|
|
92
|
+
export declare const mediaImageVariantFromQuery: (value: string) => MediaImageVariantDefinition | null;
|
|
93
|
+
export declare const mediaImageVariantUrl: (sourceUrl: string, variant: "avatar" | "card" | "full" | "hero" | "og" | "preview" | "small" | "thumb") => string;
|
|
94
|
+
export declare const responsiveMediaImageVariants: (target: "avatar" | "card" | "full" | "hero" | "og" | "preview" | "small" | "thumb") => readonly ("avatar" | "card" | "full" | "hero" | "og" | "preview" | "small" | "thumb")[];
|
|
95
|
+
/**
|
|
96
|
+
* True only for public URLs owned by Highlight's image-capable delivery
|
|
97
|
+
* Worker. The localhost forms mirror the Worker's explicit development
|
|
98
|
+
* namespaces.
|
|
99
|
+
*/
|
|
100
|
+
export declare const isHighlightMediaDeliveryUrl: (value: string, hosts?: MediaDeliveryHosts) => boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Client-side preflight for requesting a named image variant. A zero file size
|
|
103
|
+
* is currently the legacy importer's "unknown" sentinel; the delivery Worker
|
|
104
|
+
* still validates the actual R2 Content-Length before invoking Images.
|
|
105
|
+
*/
|
|
106
|
+
export declare const isMediaImageTransformable: (media: MediaAsset, source: Pick<MediaStorageLocation, "provider"> & {
|
|
107
|
+
url: string;
|
|
108
|
+
}, hosts?: MediaDeliveryHosts) => boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Public hosts used to turn a storage location into a URL. Fetch them from
|
|
111
|
+
* `config.mediaHosts` so a deployment can move a host without every consumer
|
|
112
|
+
* shipping a new SDK; the defaults below are production and exist only so
|
|
113
|
+
* simple consumers work without a config round-trip.
|
|
114
|
+
*/
|
|
115
|
+
export type MediaDeliveryHosts = {
|
|
116
|
+
assets: string;
|
|
117
|
+
metadata: string;
|
|
118
|
+
legacyAssets: string;
|
|
119
|
+
arweaveGateway: string;
|
|
120
|
+
ipfsGateway: string;
|
|
121
|
+
};
|
|
122
|
+
export declare const DEFAULT_MEDIA_DELIVERY_HOSTS: MediaDeliveryHosts;
|
|
123
|
+
/**
|
|
124
|
+
* The URL to fetch a media location from.
|
|
125
|
+
*
|
|
126
|
+
* R2 URLs are composed from the asset's own identity — a File is `/{mediaId}`,
|
|
127
|
+
* a directory child is `/{parentId}/{path}` — never by parsing the storage key.
|
|
128
|
+
* That is why a stored key can change shape without any URL changing, and why
|
|
129
|
+
* assets written under older key layouts resolve identically.
|
|
130
|
+
*
|
|
131
|
+
* This answers "what should the browser fetch right now". It is deliberately
|
|
132
|
+
* not the backend's `artifactUri`, which answers "what immutable URI do we bake
|
|
133
|
+
* into an on-chain document" — Archive-first and permanent.
|
|
134
|
+
*/
|
|
135
|
+
export declare const mediaUrl: (media: Pick<MediaAsset, "id" | "kind" | "parentId" | "path">, location: Pick<MediaStorageLocation, "provider" | "reference">, hosts?: MediaDeliveryHosts) => string | null;
|
|
2
136
|
/**
|
|
3
137
|
* Resolve the preferred public URL for a media asset. Replaces the old
|
|
4
138
|
* `MediaAsset.url` field — the preference is now explicit and opt-in rather
|
|
@@ -9,6 +143,7 @@ import { type MediaAsset, StorageRole } from "./gen/types.gen.js";
|
|
|
9
143
|
*/
|
|
10
144
|
export declare const resolveMediaUrl: (media: MediaAsset | null | undefined, options?: {
|
|
11
145
|
prefer?: StorageRole[] | StorageRole | undefined;
|
|
146
|
+
hosts?: MediaDeliveryHosts | undefined;
|
|
12
147
|
} | undefined) => string | null;
|
|
13
148
|
/**
|
|
14
149
|
* Resolve the renderable URL for a generative code bundle (a Directory media
|
|
@@ -27,5 +162,6 @@ export declare const resolveMediaUrl: (media: MediaAsset | null | undefined, opt
|
|
|
27
162
|
export declare const resolveRenderUrl: (media: MediaAsset | null | undefined, options?: {
|
|
28
163
|
entryPath?: string | undefined;
|
|
29
164
|
params?: Record<string, string> | undefined;
|
|
165
|
+
hosts?: MediaDeliveryHosts | undefined;
|
|
30
166
|
} | undefined) => string | null;
|
|
31
167
|
//# sourceMappingURL=media.d.ts.map
|
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.5.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"
|