diff --git a/CHANGELOG.md b/CHANGELOG.md index de85088..5ec1768 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,15 @@ and `latest` stayed on the non-module 1.0.8. unversioned CDN URLs get the ES module build. `latest` was moved from 1.0.8 to 1.1.11 on 2026-11-12 (`npm dist-tag add`). `module` stays on 1.1.11. A page that loads the non-module 1.0.8 with an unversioned script tag must pin it: https://unpkg.com/convertapi-js@1.0.8/lib/convertapi.js +- `ConvertApi.auth()` also takes the host as a URL: a leading `https://` or `http://` and trailing slashes + are removed, so `ConvertApi.auth('api-token', 'https://eu-v2.convertapi.com/')` sends requests to + `https://eu-v2.convertapi.com/convert/...`. Before, that form failed with "fetch failed", because the + library requested `https://https://eu-v2.convertapi.com//convert/...`. The README showed it from + 2023-07-04 until https://github.com/ConvertAPI/convertapi-library-js/pull/34. Requests always use https. +- The type declarations (`dist/*.d.ts`) keep their JSDoc comments, so editors show them; the build used to + remove them. The `auth()` comment describes the API token and the host name instead of the retired + `secret` and `apiKey`/`token` object, and `Result.duration` is described as the conversion cost + (`ConversionCost`) rather than a duration in seconds. ## 1.1.11 - 2026-10-20 diff --git a/package.json b/package.json index 4f7fa31..cc13143 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dist" ], "scripts": { - "build": "tsc", + "build": "tsc && tsc --emitDeclarationOnly --removeComments false", "test": "npm run build && node --test test/*.test.js", "prepublishOnly": "echo 'convertapi-js is published by a GitHub Release, see RELEASING.md' && exit 1" }, diff --git a/src/convertapi.ts b/src/convertapi.ts index b7c78c5..9117d3e 100644 --- a/src/convertapi.ts +++ b/src/convertapi.ts @@ -5,20 +5,28 @@ import Result, {IResultErrorDto} from "./result.js"; export default class ConvertApi { /** - * Returns ConvertApi object ready to use for conversions + * Returns a ConvertApi object ready to use for conversions. * - * @param credentials - Your ConvertAPI credentials. Should be an `Object` with the `secret` or `apiKey` and `token` properties. - * @param host - ConvertApi server domain name + * @param credentials - Your API token (https://www.convertapi.com/a/authentication), sent in the `Authorization: Bearer` header. + * @param host - API host name, without `https://` or a trailing slash: `v2.convertapi.com` (the default) or a regional host such as `eu-v2.convertapi.com` (https://www.convertapi.com/docs/servers-location). A leading `https://` or `http://` and trailing slashes are removed; requests always use https. * @returns - New ConvertApi object */ static auth(credentials: string, host?: string): ConvertApi { return new ConvertApi(credentials, host) } + /** API host name that requests go to, such as `v2.convertapi.com`, without `https://` or a trailing slash. */ + public readonly host: string + constructor( public readonly authCredentials: string, - public readonly host: string='v2.convertapi.com' - ) {} + host: string = 'v2.convertapi.com' + ) { + // Requests go to https://{host}/..., so a host given as a URL, such as 'https://eu-v2.convertapi.com/' + // (in the README until https://github.com/ConvertAPI/convertapi-library-js/pull/34), loses its scheme + // and trailing slashes instead of breaking every request. + this.host = host.replace(/^https?:\/\//i, '').replace(/\/+$/, '') + } /** * Returns initialized and ready to use new conversion parameters object. diff --git a/src/result.ts b/src/result.ts index 7cb341d..afb63e1 100644 --- a/src/result.ts +++ b/src/result.ts @@ -30,7 +30,7 @@ export default class Result { ) {} /** - * Returns conversion duration seconds + * Returns the conversion cost (`ConversionCost`), the amount deducted from your balance for this conversion. */ public get duration(): number { return this.dto.ConversionCost diff --git a/test/convertapi.test.js b/test/convertapi.test.js index 533821c..1a402ce 100644 --- a/test/convertapi.test.js +++ b/test/convertapi.test.js @@ -1,5 +1,6 @@ import { test } from 'node:test' import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' import { stubFetch } from './setup.js' import ConvertApi from '../dist/convertapi.js' import { FileValue } from '../dist/file-param.js' @@ -16,6 +17,44 @@ test('auth() keeps the token and defaults to v2.convertapi.com', () => { assert.equal(ConvertApi.auth('token-1', 'example.test').host, 'example.test') }) +test('auth() takes the host as a URL too: https://, http:// and trailing slashes are removed', () => { + const cases = [ + ['https://eu-v2.convertapi.com/', 'eu-v2.convertapi.com'], + ['https://eu-v2.convertapi.com', 'eu-v2.convertapi.com'], + ['HTTPS://eu-v2.convertapi.com/', 'eu-v2.convertapi.com'], + ['http://eu-v2.convertapi.com/', 'eu-v2.convertapi.com'], + ['eu-v2.convertapi.com//', 'eu-v2.convertapi.com'], + ['eu-v2.convertapi.com', 'eu-v2.convertapi.com'], + ['http://localhost:8080/', 'localhost:8080'] + ] + for (const [host, expected] of cases) { + assert.equal(ConvertApi.auth('token-1', host).host, expected, host) + } + assert.equal(new ConvertApi('token-1', 'https://example.test/').host, 'example.test') +}) + +test('a host given as a URL is used for uploads and conversions', async t => { + const calls = stubFetch(t, { body: { FileId: 'up-1' } }, { body: { FileId: 'up-2' } }, { body: result }) + // The form the README showed until https://github.com/ConvertAPI/convertapi-library-js/pull/34 + const api = ConvertApi.auth('token-1', 'https://example.test/') + const params = api.createParams() + params.add('File', new File(['%PDF'], 'a.pdf')) + params.add('OverlayFile', new URL('https://cdn.example.test/overlay.pdf')) + + await api.convert('pdf', 'pdf-watermark', params) + + assert.deepEqual(calls.map(c => c.url), [ + 'https://example.test/upload?filename=a.pdf', + 'https://example.test/upload?url=https%3A%2F%2Fcdn.example.test%2Foverlay.pdf', + 'https://example.test/convert/pdf/to/pdf-watermark?storefile=true' + ]) +}) + +test('the type declarations keep the JSDoc that editors show', async () => { + const dts = await readFile(new URL('../dist/convertapi.d.ts', import.meta.url), 'utf8') + assert.match(dts, /@param host - API host name, without `https:\/\/`/) +}) + test('convert() posts the parameters with a Bearer token and returns the result', async t => { const calls = stubFetch(t, { body: result }) const api = ConvertApi.auth('token-1', 'example.test') diff --git a/tsconfig.json b/tsconfig.json index 5219eb6..cc90518 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,8 @@ "noUnusedLocals": true, "strictNullChecks": true, + // The JavaScript ships without comments. `npm run build` then emits the declarations a second time + // with --removeComments false, so dist/*.d.ts keep the JSDoc that editors show. "removeComments": true, "sourceMap": true, "declaration": true,