Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ if (result.success) {
}
```

### Delete Images

Delete one or more existing images from HCTI and clear them from the CDN using their image IDs.

```typescript
const result = await client.deleteImage('your_image_id');

if (!result.success) {
console.error('Error:', result.error);
}

const batchResult = await client.deleteImageBatch([
'first_image_id',
'second_image_id'
]);
```

## Creating Image URLs

For use cases where you want to generate a URL on the server and use it safely on the client (without exposing your API key), you can generate a signed URL.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@html-css-to-image/client",
"version": "0.4.0",
"version": "0.5.0",
"type": "module",
"description": "Lightweight TypeScript client for the HTML/CSS to Image API. Generate images from HTML/CSS, URLs, or templates in your TypeScript or JavaScript projects.",
"main": "dist/index.js",
Expand Down
34 changes: 33 additions & 1 deletion src/HtmlCssToImageClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {IHtmlCssToImageClient} from "./IHtmlCssToImageClient.js";
import {CreateTemplatedImageRequest} from "./types/request.js";
import type {BaseCreateImageRequest, CreateHtmlCssImageRequest, CreateUrlImageRequest, PDFOptions, PdfValueInput} from "./types/request.js";
import type {CreateImageBatchResponse, CreateImageBatchSuccessResponse, CreateImageResponse} from "./types/response.js";
import type {CreateImageBatchResponse, CreateImageBatchSuccessResponse, CreateImageErrorResponse, CreateImageResponse, DeleteImageResponse} from "./types/response.js";
import * as crypto from 'node:crypto';

export type FetchFunction = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
Expand Down Expand Up @@ -96,6 +96,30 @@ export class HtmlCssToImageClient implements IHtmlCssToImageClient {

}

async deleteImage(imageId: string): Promise<DeleteImageResponse> {
const response = await this.fetch(`${this.baseUrl}/v1/image/${encodeURIComponent(imageId)}`, {
method: 'DELETE',
headers: {
'Authorization': this.authHeader,
},
});

return await this.responseToDeleteImageResponse(response);
}

async deleteImageBatch(imageIds: readonly string[]): Promise<DeleteImageResponse> {
const response = await this.fetch(`${this.baseUrl}/v1/image/batch`, {
method: 'DELETE',
headers: {
'Authorization': this.authHeader,
'Content-Type': 'application/json',
},
body: JSON.stringify({ids: imageIds}),
});

return await this.responseToDeleteImageResponse(response);
}

private mapToInternal(request: CreateHtmlCssImageRequest | CreateUrlImageRequest | CreateTemplatedImageRequest, in_batch: boolean): any {
switch (request.__type) {
case 'html_css':
Expand Down Expand Up @@ -210,6 +234,14 @@ export class HtmlCssToImageClient implements IHtmlCssToImageClient {
}
}

private async responseToDeleteImageResponse(response: Response): Promise<DeleteImageResponse> {
if (response.ok) {
return {success: true};
}

return await this.responseToType<CreateImageErrorResponse>(response);
}

private mapHtmlCssToInternalRequest(request: CreateHtmlCssImageRequest, in_batch: boolean): InternalCreateHtmlCssImageRequest | InternalCreateHtmlCssImageRequestWithOptionalHtml {
const {pdf_options, google_fonts, __type, ...shared_request} = request;
let new_request: InternalCreateHtmlCssImageRequest|InternalCreateHtmlCssImageRequestWithOptionalHtml;
Expand Down
4 changes: 3 additions & 1 deletion src/IHtmlCssToImageClient.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type {CreateHtmlCssImageRequest, CreateTemplatedImageRequest, CreateUrlImageRequest} from "./types/request.js";
import type {CreateImageBatchResponse, CreateImageResponse} from "./types/response.js";
import type {CreateImageBatchResponse, CreateImageResponse, DeleteImageResponse} from "./types/response.js";

export interface IHtmlCssToImageClient {
createImage(request: CreateHtmlCssImageRequest|CreateUrlImageRequest|CreateTemplatedImageRequest) : Promise<CreateImageResponse>;
createImageBatch<T extends CreateHtmlCssImageRequest|CreateUrlImageRequest>(variations: T[],default_options?:T) : Promise<CreateImageBatchResponse>;
deleteImage(imageId: string): Promise<DeleteImageResponse>;
deleteImageBatch(imageIds: readonly string[]): Promise<DeleteImageResponse>;
generateCreateAndRenderUrl(request: CreateUrlImageRequest): string;
generateTemplatedImageUrl<T extends Record<string,any>>(template_id: string, template_values: T, template_version?: number):string;
generateTemplatedImageUrl(request: CreateTemplatedImageRequest):string;
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export type {
CreateImageErrorResponse,
CreateImageResponse,
CreateImageSuccessResponse,
DeleteImageResponse,
DeleteImageSuccessResponse,
ValidationError
} from './types/response.js';
export { HtmlCssToImageClient } from './HtmlCssToImageClient.js';
Expand Down
8 changes: 7 additions & 1 deletion src/types/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export interface CreateImageBatchSuccessResponse{
images: CreateImageSuccessResponse[];
}

export interface DeleteImageSuccessResponse {
success: true;
}

export type CreateImageBatchResponse = CreateImageBatchSuccessResponse | CreateImageErrorResponse;

export type CreateImageResponse = CreateImageSuccessResponse | CreateImageErrorResponse;
export type CreateImageResponse = CreateImageSuccessResponse | CreateImageErrorResponse;

export type DeleteImageResponse = DeleteImageSuccessResponse | CreateImageErrorResponse;
61 changes: 61 additions & 0 deletions tests/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,67 @@ describe('HtmlCssToImageClient', () => {
assert.strictEqual(result.success, true);
});

test('deleteImage sends an authenticated DELETE request', async () => {
const mockFetch = async (url: string, options: any) => {
assert.strictEqual(url, 'https://hcti.io/v1/image/image%2Fid');
assert.strictEqual(options.method, 'DELETE');
assert.strictEqual(options.headers.Authorization, `Basic ${Buffer.from(`${apiId}:${apiKey}`).toString('base64')}`);
assert.strictEqual(options.body, undefined);

return {
ok: true,
status: 202,
} as Response;
};

const client = new HtmlCssToImageClient(apiId, apiKey, mockFetch as any);
const result = await client.deleteImage('image/id');

assert.deepStrictEqual(result, {success: true});
});

test('deleteImage returns API error details', async () => {
const mockFetch = async () => ({
ok: false,
status: 404,
json: async () => ({
error: 'Not Found',
message: 'Image not found',
}),
} as Response);

const client = new HtmlCssToImageClient(apiId, apiKey, mockFetch as any);
const result = await client.deleteImage('missing-image');

assert.strictEqual(result.success, false);
if (!result.success) {
assert.strictEqual(result.error, 'Not Found');
assert.strictEqual(result.message, 'Image not found');
}
});

test('deleteImageBatch sends image IDs in an authenticated DELETE request', async () => {
const mockFetch = async (url: string, options: any) => {
assert.strictEqual(url, 'https://hcti.io/v1/image/batch');
assert.strictEqual(options.method, 'DELETE');
assert.strictEqual(options.headers.Authorization, `Basic ${Buffer.from(`${apiId}:${apiKey}`).toString('base64')}`);
assert.strictEqual(options.headers['Content-Type'], 'application/json');
assert.deepStrictEqual(JSON.parse(options.body), {
ids: ['image-1', 'image-2'],
});

return {
ok: true,
status: 202,
} as Response;
};

const client = new HtmlCssToImageClient(apiId, apiKey, mockFetch as any);
const result = await client.deleteImageBatch(['image-1', 'image-2']);

assert.deepStrictEqual(result, {success: true});
});

test('generateCreateAndRenderUrl includes CSS and an explicit transparent background value', () => {
const client = new HtmlCssToImageClient(apiId, apiKey);
const url = client.generateCreateAndRenderUrl(new CreateUrlImageRequest({
Expand Down
Loading