@immagin/client 0.2.1 → 0.2.4
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 +68 -59
- package/dist/index.d.mts +119 -13
- package/dist/index.mjs +99 -37
- package/package.json +7 -8
package/README.md
CHANGED
|
@@ -17,97 +17,87 @@ import { Immagin } from '@immagin/client'
|
|
|
17
17
|
|
|
18
18
|
const client = new Immagin({ apiKey: 'imk_...' })
|
|
19
19
|
|
|
20
|
-
//
|
|
21
|
-
const url = await client.images.url('photos/hero.jpg',
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
// Generate a signed image URL (auto-fetches tenant credentials)
|
|
21
|
+
const url = await client.images.url('photos/hero.jpg', [
|
|
22
|
+
{ resize: { width: 800, height: 600 } },
|
|
23
|
+
])
|
|
24
24
|
|
|
25
|
-
// Upload an image
|
|
25
|
+
// Upload an image (auto-detects MIME type)
|
|
26
26
|
import { readFileSync } from 'node:fs'
|
|
27
27
|
const buffer = readFileSync('photo.jpg')
|
|
28
|
-
await client.images.upload(buffer,
|
|
29
|
-
key: 'photos/hero.jpg',
|
|
30
|
-
contentType: 'image/jpeg',
|
|
31
|
-
})
|
|
28
|
+
await client.images.upload(buffer, 'photos/hero.jpg')
|
|
32
29
|
```
|
|
33
30
|
|
|
34
31
|
## Images
|
|
35
32
|
|
|
36
|
-
###
|
|
33
|
+
### Generate image URLs
|
|
37
34
|
|
|
38
|
-
|
|
35
|
+
Generate signed image URLs for serving processed images. All URLs are automatically signed — the signature locks transformation parameters so they can't be tampered with.
|
|
39
36
|
|
|
40
37
|
```ts
|
|
38
|
+
// Original image
|
|
41
39
|
const url = await client.images.url('photo.jpg')
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
With transformations:
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
const url = await client.images.url('photo.jpg', {
|
|
48
|
-
size: [400, 300],
|
|
49
|
-
text: {
|
|
50
|
-
text: 'Hello',
|
|
51
|
-
position: 'bottom-right', // top-left, top-right, bottom-left, bottom-right, center
|
|
52
|
-
fontSize: 24,
|
|
53
|
-
color: '#ffffff',
|
|
54
|
-
opacity: 0.8,
|
|
55
|
-
},
|
|
56
|
-
})
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Sign URLs locally (without API call)
|
|
60
|
-
|
|
61
|
-
Generate signed image URLs on the server without making an API request. Requires `tenantId` and `tenantSecret` in the constructor.
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
const client = new Immagin({
|
|
65
|
-
apiKey: 'imk_...',
|
|
66
|
-
tenantId: 'your-tenant-id',
|
|
67
|
-
tenantSecret: 'your-tenant-secret',
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
// Synchronous - no network request
|
|
71
|
-
const url = client.images.signedUrl('photo.jpg')
|
|
72
40
|
|
|
73
41
|
// With transformations
|
|
74
|
-
const thumb = client.images.
|
|
75
|
-
|
|
76
|
-
text: { text: '© My Company', position: 'bottom-right', opacity: 0.5 },
|
|
77
|
-
|
|
42
|
+
const thumb = await client.images.url('photo.jpg', [
|
|
43
|
+
{ resize: { width: 800, height: 600 } },
|
|
44
|
+
{ text: { text: '© My Company', position: 'bottom-right', opacity: 0.5 } },
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
// Rotate, blur, grayscale
|
|
48
|
+
const edited = await client.images.url('photo.jpg', [
|
|
49
|
+
{ rotate: { angle: 90 } },
|
|
50
|
+
{ blur: 3 },
|
|
51
|
+
{ grayscale: true },
|
|
52
|
+
])
|
|
53
|
+
|
|
54
|
+
// Crop a region
|
|
55
|
+
const cropped = await client.images.url('photo.jpg', [
|
|
56
|
+
{ crop: { left: 100, top: 50, width: 400, height: 300 } },
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
// Flip, sharpen, adjust colors
|
|
60
|
+
const adjusted = await client.images.url('photo.jpg', [
|
|
61
|
+
{ flip: true },
|
|
62
|
+
{ sharpen: 2 },
|
|
63
|
+
{ modulate: { brightness: 1.2, saturation: 0.8 } },
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
// Custom output format (default is WebP at quality 90)
|
|
67
|
+
const jpeg = await client.images.url(
|
|
68
|
+
'photo.jpg',
|
|
69
|
+
[{ resize: { width: 800 } }],
|
|
70
|
+
{ format: 'jpeg', quality: 85 },
|
|
71
|
+
)
|
|
78
72
|
```
|
|
79
73
|
|
|
80
|
-
> **Note:**
|
|
74
|
+
> **Note:** `url()` uses `node:crypto` for signing and is intended for server-side use only. Tenant credentials are auto-fetched and cached on initialization.
|
|
81
75
|
|
|
82
|
-
### Get a
|
|
76
|
+
### Get a signed upload URL
|
|
83
77
|
|
|
84
|
-
Returns a
|
|
78
|
+
Returns a CloudFront signed URL for direct upload (expires in 5 minutes). Useful for browser uploads where you don't want to expose your API key. The URL points to a CloudFront distribution (not S3 directly), so the underlying infrastructure is not exposed.
|
|
85
79
|
|
|
86
80
|
```ts
|
|
87
|
-
// Server: get the
|
|
88
|
-
const { uploadUrl, key } = await client.images.signUrl('photos/hero.jpg'
|
|
81
|
+
// Server: get the signed upload URL
|
|
82
|
+
const { uploadUrl, key } = await client.images.signUrl('photos/hero.jpg')
|
|
89
83
|
// Return uploadUrl to the browser
|
|
90
84
|
|
|
91
|
-
// Browser: upload directly
|
|
85
|
+
// Browser: upload directly
|
|
92
86
|
await fetch(uploadUrl, {
|
|
93
87
|
method: 'PUT',
|
|
94
88
|
body: file,
|
|
95
|
-
headers: { 'content-type': 'image/jpeg' },
|
|
96
89
|
})
|
|
97
90
|
```
|
|
98
91
|
|
|
99
92
|
### Upload (Node.js)
|
|
100
93
|
|
|
101
|
-
Convenience method that gets a signed URL and uploads in one call. The file goes directly to S3 and never passes through the API.
|
|
94
|
+
Convenience method that gets a signed URL and uploads in one call. The file goes directly to CloudFront/S3 and never passes through the API. MIME type is auto-detected from file bytes (magic bytes for JPEG, PNG, GIF, WebP, BMP, AVIF, HEIC, HEIF) or file extension.
|
|
102
95
|
|
|
103
96
|
```ts
|
|
104
97
|
import { readFileSync } from 'node:fs'
|
|
105
98
|
|
|
106
99
|
const buffer = readFileSync('photo.jpg')
|
|
107
|
-
await client.images.upload(buffer,
|
|
108
|
-
key: 'uploads/photo.jpg',
|
|
109
|
-
contentType: 'image/jpeg',
|
|
110
|
-
})
|
|
100
|
+
await client.images.upload(buffer, 'uploads/photo.jpg')
|
|
111
101
|
```
|
|
112
102
|
|
|
113
103
|
### List
|
|
@@ -130,6 +120,27 @@ const page = await client.images.list({
|
|
|
130
120
|
await client.images.delete('uploads/photo.jpg')
|
|
131
121
|
```
|
|
132
122
|
|
|
123
|
+
### Metadata
|
|
124
|
+
|
|
125
|
+
Inspect image properties (dimensions, format, color space, etc.) without downloading the full image. The request goes through Luna, which extracts metadata using Sharp.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const meta = await client.images.metadata('photo.jpg')
|
|
129
|
+
// {
|
|
130
|
+
// width: 1920,
|
|
131
|
+
// height: 1080,
|
|
132
|
+
// format: 'jpeg',
|
|
133
|
+
// space: 'srgb',
|
|
134
|
+
// channels: 3,
|
|
135
|
+
// hasAlpha: false,
|
|
136
|
+
// density: 72,
|
|
137
|
+
// isProgressive: false,
|
|
138
|
+
// size: 204800,
|
|
139
|
+
// }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
> **Note:** `metadata()` uses `node:crypto` for signing and is intended for server-side use only.
|
|
143
|
+
|
|
133
144
|
## API Keys
|
|
134
145
|
|
|
135
146
|
Manage API keys programmatically.
|
|
@@ -178,8 +189,6 @@ try {
|
|
|
178
189
|
const client = new Immagin({
|
|
179
190
|
apiKey: 'imk_...', // Required
|
|
180
191
|
baseUrl: 'https://...', // Optional, defaults to https://gateway.immag.in
|
|
181
|
-
tenantId: 'your-tenant-id', // Optional, required for signedUrl()
|
|
182
|
-
tenantSecret: 'your-secret', // Optional, required for signedUrl()
|
|
183
192
|
})
|
|
184
193
|
```
|
|
185
194
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/types.d.ts
|
|
2
|
-
type TextPosition = 'top-left' | 'top-right' | '
|
|
2
|
+
type TextPosition = 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
3
3
|
interface TextOverlay {
|
|
4
4
|
text: string;
|
|
5
5
|
fontSize?: number;
|
|
@@ -8,14 +8,110 @@ interface TextOverlay {
|
|
|
8
8
|
position?: TextPosition;
|
|
9
9
|
padding?: number;
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
type ResizeFit = 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
|
|
12
|
+
type ResizePosition = 'top' | 'right top' | 'right' | 'right bottom' | 'bottom' | 'left bottom' | 'left' | 'left top' | 'centre' | 'center' | 'entropy' | 'attention';
|
|
13
|
+
type ResizeKernel = 'nearest' | 'cubic' | 'mitchell' | 'lanczos2' | 'lanczos3';
|
|
14
|
+
interface ResizeOptions {
|
|
15
|
+
width: number;
|
|
16
|
+
height?: number;
|
|
17
|
+
fit?: ResizeFit;
|
|
18
|
+
position?: ResizePosition;
|
|
19
|
+
kernel?: ResizeKernel;
|
|
20
|
+
background?: string;
|
|
21
|
+
upscale?: boolean;
|
|
22
|
+
downscale?: boolean;
|
|
14
23
|
}
|
|
15
|
-
interface
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
interface RotateOptions {
|
|
25
|
+
angle: number;
|
|
26
|
+
background?: string;
|
|
27
|
+
}
|
|
28
|
+
interface CropOptions {
|
|
29
|
+
left: number;
|
|
30
|
+
top: number;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
}
|
|
34
|
+
interface ExtendOptions {
|
|
35
|
+
top?: number;
|
|
36
|
+
bottom?: number;
|
|
37
|
+
left?: number;
|
|
38
|
+
right?: number;
|
|
39
|
+
background?: string;
|
|
40
|
+
}
|
|
41
|
+
interface ModulateOptions {
|
|
42
|
+
brightness?: number;
|
|
43
|
+
saturation?: number;
|
|
44
|
+
hue?: number;
|
|
45
|
+
lightness?: number;
|
|
46
|
+
}
|
|
47
|
+
interface FlattenOptions {
|
|
48
|
+
background?: string;
|
|
49
|
+
}
|
|
50
|
+
interface TintOptions {
|
|
51
|
+
r: number;
|
|
52
|
+
g: number;
|
|
53
|
+
b: number;
|
|
54
|
+
}
|
|
55
|
+
interface NormalizeOptions {
|
|
56
|
+
lower?: number;
|
|
57
|
+
upper?: number;
|
|
58
|
+
}
|
|
59
|
+
interface TrimOptions {
|
|
60
|
+
background?: string;
|
|
61
|
+
threshold?: number;
|
|
62
|
+
}
|
|
63
|
+
type ImageOperation = {
|
|
64
|
+
resize: ResizeOptions;
|
|
65
|
+
} | {
|
|
66
|
+
text: TextOverlay;
|
|
67
|
+
} | {
|
|
68
|
+
rotate: RotateOptions;
|
|
69
|
+
} | {
|
|
70
|
+
flip: true;
|
|
71
|
+
} | {
|
|
72
|
+
flop: true;
|
|
73
|
+
} | {
|
|
74
|
+
blur: number;
|
|
75
|
+
} | {
|
|
76
|
+
sharpen: number;
|
|
77
|
+
} | {
|
|
78
|
+
grayscale: true;
|
|
79
|
+
} | {
|
|
80
|
+
crop: CropOptions;
|
|
81
|
+
} | {
|
|
82
|
+
extend: ExtendOptions;
|
|
83
|
+
} | {
|
|
84
|
+
modulate: ModulateOptions;
|
|
85
|
+
} | {
|
|
86
|
+
flatten: FlattenOptions;
|
|
87
|
+
} | {
|
|
88
|
+
tint: TintOptions;
|
|
89
|
+
} | {
|
|
90
|
+
invert: boolean;
|
|
91
|
+
} | {
|
|
92
|
+
normalize: NormalizeOptions;
|
|
93
|
+
} | {
|
|
94
|
+
trim: TrimOptions;
|
|
95
|
+
};
|
|
96
|
+
type OutputFormat = 'webp' | 'jpeg' | 'png' | 'gif' | 'jp2' | 'tiff' | 'avif' | 'heif';
|
|
97
|
+
interface OutputOptions {
|
|
98
|
+
format?: OutputFormat;
|
|
99
|
+
quality?: number;
|
|
100
|
+
progressive?: boolean;
|
|
101
|
+
lossless?: boolean;
|
|
102
|
+
}
|
|
103
|
+
interface ImageMetadata {
|
|
104
|
+
width: number;
|
|
105
|
+
height: number;
|
|
106
|
+
format: string;
|
|
107
|
+
space: string;
|
|
108
|
+
channels: number;
|
|
109
|
+
hasAlpha: boolean;
|
|
110
|
+
orientation?: number;
|
|
111
|
+
density?: number;
|
|
112
|
+
isProgressive?: boolean;
|
|
113
|
+
pages?: number;
|
|
114
|
+
size: number;
|
|
19
115
|
}
|
|
20
116
|
interface UploadResult {
|
|
21
117
|
uploadUrl: string;
|
|
@@ -47,6 +143,10 @@ interface CreateKeyResult {
|
|
|
47
143
|
prefix: string;
|
|
48
144
|
name: string;
|
|
49
145
|
}
|
|
146
|
+
interface ProjectInfo {
|
|
147
|
+
tenantId: string;
|
|
148
|
+
tenantSecret: string;
|
|
149
|
+
}
|
|
50
150
|
interface ImmaginConfig {
|
|
51
151
|
apiKey: string;
|
|
52
152
|
baseUrl?: string;
|
|
@@ -58,12 +158,12 @@ interface ImmaginConfig {
|
|
|
58
158
|
declare class ImagesResource {
|
|
59
159
|
private client;
|
|
60
160
|
constructor(client: Immagin);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
upload(file: Blob | Buffer | ReadableStream, options: UploadOptions): Promise<UploadResult>;
|
|
161
|
+
url(key: string, edits?: ImageOperation | ImageOperation[], output?: OutputOptions): Promise<string>;
|
|
162
|
+
signUrl(key: string): Promise<UploadResult>;
|
|
163
|
+
upload(file: Blob | Buffer | ReadableStream, key: string): Promise<UploadResult>;
|
|
65
164
|
list(options?: ListImagesOptions): Promise<ListImagesResult>;
|
|
66
165
|
delete(key: string): Promise<void>;
|
|
166
|
+
metadata(key: string): Promise<ImageMetadata>;
|
|
67
167
|
}
|
|
68
168
|
//#endregion
|
|
69
169
|
//#region src/resources/keys.d.ts
|
|
@@ -85,10 +185,16 @@ declare class Immagin {
|
|
|
85
185
|
tenantId?: string;
|
|
86
186
|
/** @internal */
|
|
87
187
|
tenantSecret?: string;
|
|
188
|
+
private _tenantInfoPromise?;
|
|
88
189
|
images: ImagesResource;
|
|
89
190
|
keys: KeysResource;
|
|
90
191
|
constructor(config: ImmaginConfig);
|
|
91
192
|
/** @internal */
|
|
193
|
+
resolveTenantInfo(): Promise<{
|
|
194
|
+
tenantId: string;
|
|
195
|
+
tenantSecret: string;
|
|
196
|
+
}>;
|
|
197
|
+
/** @internal */
|
|
92
198
|
request<T>(method: string, path: string, options?: {
|
|
93
199
|
body?: unknown;
|
|
94
200
|
params?: Record<string, string>;
|
|
@@ -102,4 +208,4 @@ declare class ImmaginError extends Error {
|
|
|
102
208
|
constructor(message: string, status: number, body?: unknown | undefined);
|
|
103
209
|
}
|
|
104
210
|
//#endregion
|
|
105
|
-
export { type ApiKey, type CreateKeyResult, type
|
|
211
|
+
export { type ApiKey, type CreateKeyResult, type CropOptions, type ExtendOptions, type FlattenOptions, type ImageEntry, type ImageMetadata, type ImageOperation, Immagin, type ImmaginConfig, ImmaginError, type ListImagesOptions, type ListImagesResult, type ModulateOptions, type NormalizeOptions, type OutputFormat, type OutputOptions, type ProjectInfo, type ResizeFit, type ResizeKernel, type ResizeOptions, type ResizePosition, type RotateOptions, type TextOverlay, type TextPosition, type TintOptions, type TrimOptions, type UploadResult };
|
package/dist/index.mjs
CHANGED
|
@@ -1,42 +1,82 @@
|
|
|
1
1
|
import { createHmac } from "node:crypto";
|
|
2
2
|
|
|
3
|
+
//#region src/errors.ts
|
|
4
|
+
var ImmaginError = class extends Error {
|
|
5
|
+
constructor(message, status, body) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.body = body;
|
|
9
|
+
this.name = "ImmaginError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
3
14
|
//#region src/resources/images.ts
|
|
15
|
+
const MIME_TYPES = {
|
|
16
|
+
jpg: "image/jpeg",
|
|
17
|
+
jpeg: "image/jpeg",
|
|
18
|
+
png: "image/png",
|
|
19
|
+
gif: "image/gif",
|
|
20
|
+
webp: "image/webp",
|
|
21
|
+
avif: "image/avif",
|
|
22
|
+
svg: "image/svg+xml",
|
|
23
|
+
tiff: "image/tiff",
|
|
24
|
+
tif: "image/tiff",
|
|
25
|
+
bmp: "image/bmp",
|
|
26
|
+
ico: "image/x-icon",
|
|
27
|
+
heic: "image/heic",
|
|
28
|
+
heif: "image/heif"
|
|
29
|
+
};
|
|
30
|
+
function mimeFromKey(key) {
|
|
31
|
+
const ext = key.split(".").pop()?.toLowerCase();
|
|
32
|
+
return ext ? MIME_TYPES[ext] : void 0;
|
|
33
|
+
}
|
|
34
|
+
function mimeFromBuffer(buf) {
|
|
35
|
+
if (buf.length < 12) return void 0;
|
|
36
|
+
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "image/jpeg";
|
|
37
|
+
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
|
|
38
|
+
if (buf[0] === 71 && buf[1] === 73 && buf[2] === 70) return "image/gif";
|
|
39
|
+
if (buf[0] === 82 && buf[1] === 73 && buf[2] === 70 && buf[3] === 70 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) return "image/webp";
|
|
40
|
+
if (buf[0] === 66 && buf[1] === 77) return "image/bmp";
|
|
41
|
+
if (buf[4] === 102 && buf[5] === 116 && buf[6] === 121 && buf[7] === 112) {
|
|
42
|
+
const brand = buf.slice(8, 12).toString("ascii");
|
|
43
|
+
if (brand === "avif" || brand === "avis") return "image/avif";
|
|
44
|
+
if (brand === "heic" || brand === "heix") return "image/heic";
|
|
45
|
+
if (brand === "heif" || brand === "mif1") return "image/heif";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function mimeFromFile(file) {
|
|
49
|
+
if (typeof Blob !== "undefined" && file instanceof Blob && file.type) return file.type;
|
|
50
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(file)) return mimeFromBuffer(file);
|
|
51
|
+
}
|
|
4
52
|
var ImagesResource = class {
|
|
5
53
|
constructor(client) {
|
|
6
54
|
this.client = client;
|
|
7
55
|
}
|
|
8
|
-
|
|
9
|
-
const { tenantId, tenantSecret } = this.client;
|
|
10
|
-
if (!tenantId || !tenantSecret) throw new Error("tenantId and tenantSecret are required for signedUrl(). Pass them in the Immagin constructor.");
|
|
56
|
+
async url(key, edits, output) {
|
|
57
|
+
const { tenantId, tenantSecret } = await this.client.resolveTenantInfo();
|
|
11
58
|
const payload = { key };
|
|
12
|
-
if (edits
|
|
59
|
+
if (edits) {
|
|
60
|
+
const editsArray = Array.isArray(edits) ? edits : [edits];
|
|
61
|
+
if (editsArray.length > 0) payload.edits = editsArray;
|
|
62
|
+
}
|
|
63
|
+
if (output) payload.output = output;
|
|
13
64
|
const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
14
65
|
return `https://${tenantId}.immag.in/${base64}?sig=${createHmac("sha256", tenantSecret).update(base64).digest("hex").slice(0, 16)}`;
|
|
15
66
|
}
|
|
16
|
-
async
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
params.width = String(options.size[0]);
|
|
20
|
-
if (options.size.length === 2) params.height = String(options.size[1]);
|
|
21
|
-
}
|
|
22
|
-
if (options?.text) {
|
|
23
|
-
params["text.text"] = options.text.text;
|
|
24
|
-
if (options.text.position) params["text.position"] = options.text.position;
|
|
25
|
-
if (options.text.fontSize) params["text.fontSize"] = String(options.text.fontSize);
|
|
26
|
-
if (options.text.opacity) params["text.opacity"] = String(options.text.opacity);
|
|
27
|
-
if (options.text.color) params["text.color"] = options.text.color;
|
|
28
|
-
}
|
|
29
|
-
return (await this.client.request("GET", "/v1/images/url", { params })).url;
|
|
30
|
-
}
|
|
31
|
-
async signUrl(key, contentType) {
|
|
32
|
-
return this.client.request("POST", "/v1/images/sign-url", { body: {
|
|
67
|
+
async signUrl(key) {
|
|
68
|
+
const contentType = mimeFromKey(key);
|
|
69
|
+
return this.client.request("POST", "/v1/images/sign-url", { body: contentType ? {
|
|
33
70
|
key,
|
|
34
|
-
contentType
|
|
35
|
-
} });
|
|
71
|
+
contentType
|
|
72
|
+
} : { key } });
|
|
36
73
|
}
|
|
37
|
-
async upload(file,
|
|
38
|
-
const
|
|
39
|
-
const signResult = await this.
|
|
74
|
+
async upload(file, key) {
|
|
75
|
+
const contentType = mimeFromFile(file) ?? mimeFromKey(key);
|
|
76
|
+
const signResult = await this.client.request("POST", "/v1/images/sign-url", { body: contentType ? {
|
|
77
|
+
key,
|
|
78
|
+
contentType
|
|
79
|
+
} : { key } });
|
|
40
80
|
await fetch(signResult.uploadUrl, {
|
|
41
81
|
method: "PUT",
|
|
42
82
|
body: file,
|
|
@@ -54,6 +94,21 @@ var ImagesResource = class {
|
|
|
54
94
|
async delete(key) {
|
|
55
95
|
await this.client.request("DELETE", `/v1/images/${key}`);
|
|
56
96
|
}
|
|
97
|
+
async metadata(key) {
|
|
98
|
+
const { tenantId, tenantSecret } = await this.client.resolveTenantInfo();
|
|
99
|
+
const payload = JSON.stringify({
|
|
100
|
+
key,
|
|
101
|
+
metadata: true
|
|
102
|
+
});
|
|
103
|
+
const base64 = Buffer.from(payload).toString("base64");
|
|
104
|
+
const url = `https://${tenantId}.immag.in/${base64}?sig=${createHmac("sha256", tenantSecret).update(base64).digest("hex").slice(0, 16)}`;
|
|
105
|
+
const response = await fetch(url);
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const body = await response.text();
|
|
108
|
+
throw new ImmaginError(`HTTP ${response.status}`, response.status, body);
|
|
109
|
+
}
|
|
110
|
+
return response.json();
|
|
111
|
+
}
|
|
57
112
|
};
|
|
58
113
|
|
|
59
114
|
//#endregion
|
|
@@ -73,17 +128,6 @@ var KeysResource = class {
|
|
|
73
128
|
}
|
|
74
129
|
};
|
|
75
130
|
|
|
76
|
-
//#endregion
|
|
77
|
-
//#region src/errors.ts
|
|
78
|
-
var ImmaginError = class extends Error {
|
|
79
|
-
constructor(message, status, body) {
|
|
80
|
-
super(message);
|
|
81
|
-
this.status = status;
|
|
82
|
-
this.body = body;
|
|
83
|
-
this.name = "ImmaginError";
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
131
|
//#endregion
|
|
88
132
|
//#region src/client.ts
|
|
89
133
|
const DEFAULT_BASE_URL = "https://gateway.immag.in";
|
|
@@ -94,6 +138,7 @@ var Immagin = class {
|
|
|
94
138
|
tenantId;
|
|
95
139
|
/** @internal */
|
|
96
140
|
tenantSecret;
|
|
141
|
+
_tenantInfoPromise;
|
|
97
142
|
images;
|
|
98
143
|
keys;
|
|
99
144
|
constructor(config) {
|
|
@@ -103,6 +148,23 @@ var Immagin = class {
|
|
|
103
148
|
this.tenantSecret = config.tenantSecret;
|
|
104
149
|
this.images = new ImagesResource(this);
|
|
105
150
|
this.keys = new KeysResource(this);
|
|
151
|
+
if (!this.tenantId || !this.tenantSecret) this.resolveTenantInfo().catch(() => {});
|
|
152
|
+
}
|
|
153
|
+
/** @internal */
|
|
154
|
+
async resolveTenantInfo() {
|
|
155
|
+
if (this.tenantId && this.tenantSecret) return {
|
|
156
|
+
tenantId: this.tenantId,
|
|
157
|
+
tenantSecret: this.tenantSecret
|
|
158
|
+
};
|
|
159
|
+
if (!this._tenantInfoPromise) this._tenantInfoPromise = this.request("GET", "/v1/project").then((info) => {
|
|
160
|
+
this.tenantId = info.tenantId;
|
|
161
|
+
this.tenantSecret = info.tenantSecret;
|
|
162
|
+
return info;
|
|
163
|
+
}).catch((err) => {
|
|
164
|
+
this._tenantInfoPromise = void 0;
|
|
165
|
+
throw err;
|
|
166
|
+
});
|
|
167
|
+
return this._tenantInfoPromise;
|
|
106
168
|
}
|
|
107
169
|
/** @internal */
|
|
108
170
|
async request(method, path, options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@immagin/client",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Node.js and browser client for the Immagin image processing API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,15 +34,14 @@
|
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"build": "tsdown",
|
|
39
|
-
"dev": "tsdown --watch",
|
|
40
|
-
"test": "vitest run",
|
|
41
|
-
"prepublishOnly": "tsdown"
|
|
42
|
-
},
|
|
43
37
|
"devDependencies": {
|
|
44
38
|
"tsdown": "^0.20.3",
|
|
45
39
|
"typescript": "^5.7.2",
|
|
46
40
|
"vitest": "^3.0.5"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsdown",
|
|
44
|
+
"dev": "tsdown --watch",
|
|
45
|
+
"test": "vitest run"
|
|
47
46
|
}
|
|
48
|
-
}
|
|
47
|
+
}
|