Skip to content

Commit d28a5b9

Browse files
authored
Merge pull request #46 from KnorpelSenf/master
Add TypeScript typings
2 parents 0e76bd6 + caadae8 commit d28a5b9

23 files changed

Lines changed: 1174 additions & 634 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.DS_Store
22
.idea
33
node_modules/
4+
built

README.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,24 +137,40 @@ const signingSecret = '...'; // You can find it in your webhook settings.
137137
const isValid = cloudConvert.webhooks.verify(payloadString, signature, signingSecret); // returns true or false
138138
```
139139

140-
## Unit Tests
140+
## Contributing
141141

142-
Tests are based on mocha:
142+
This section is intended for people how want to contribute to the development of this library.
143143

144-
npm run test
144+
### Getting started
145+
146+
Begin with installing the necessary dependencies by running
147+
148+
npm install
149+
150+
in the root directory of this repository.
151+
152+
### Building
153+
154+
This project is written in TypeScript so it needs to be compiled first:
155+
156+
npm run build
145157

158+
This will compile the code in the `lib` directory and generate a `built` directory containing the JS files and the type declarations.
146159

160+
### Unit Tests
147161

148-
## Integration Tests
162+
Tests are based on mocha:
163+
164+
npm run test
165+
166+
### Integration Tests
149167

150168
npm run test-integration
151-
152-
169+
153170
By default, this runs the integration tests against the Sandbox API with an official CloudConvert account. If you would like to use your own account, you can set your API key using the `CLOUDCONVERT_API_KEY` enviroment variable. In this case you need to whitelist the following MD5 hashes for Sandbox API (using the CloudConvert dashboard).
154171

155172
53d6fe6b688c31c565907c81de625046 input.pdf
156173
99d4c165f77af02015aa647770286cf9 input.png
157-
158174

159175
## Resources
160176

lib/CloudConvert.js

Lines changed: 0 additions & 73 deletions
This file was deleted.

lib/CloudConvert.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import axios, { AxiosInstance } from "axios";
2+
import io from 'socket.io-client';
3+
import JobsResource, { JobEventData } from "./JobsResource";
4+
import TasksResource, { TaskEventData } from "./TasksResource";
5+
import UsersResource from "./UsersResource";
6+
import WebhooksResource from "./WebhooksResource";
7+
8+
export default class CloudConvert {
9+
private socket: SocketIOClient.Socket | undefined;
10+
private subscribedChannels: Map<string, boolean> | undefined;
11+
12+
public readonly apiKey: string;
13+
public readonly useSandbox: boolean;
14+
15+
public axios!: AxiosInstance;
16+
public tasks!: TasksResource;
17+
public jobs!: JobsResource;
18+
public users!: UsersResource;
19+
public webhooks!: WebhooksResource;
20+
21+
constructor(apiKey: string, useSandbox = false) {
22+
23+
this.apiKey = apiKey;
24+
this.useSandbox = useSandbox;
25+
26+
this.createAxiosInstance();
27+
this.createResources();
28+
29+
}
30+
31+
32+
createAxiosInstance(): void {
33+
this.axios = axios.create({
34+
baseURL: this.useSandbox ? 'https://api.sandbox.cloudconvert.com/v2/' : 'https://api.cloudconvert.com/v2/',
35+
headers: {
36+
'Authorization': 'Bearer ' + this.apiKey,
37+
'User-Agent': 'cloudconvert-node/v2 (https://github.com/cloudconvert/cloudconvert-node)'
38+
}
39+
});
40+
}
41+
42+
createResources(): void {
43+
this.tasks = new TasksResource(this);
44+
this.jobs = new JobsResource(this);
45+
this.users = new UsersResource(this);
46+
this.webhooks = new WebhooksResource(this);
47+
}
48+
49+
50+
subscribe(channel: string, event: string, callback: ((event: JobEventData) => void) | ((event: TaskEventData) => void)): void {
51+
52+
if (!this.socket) {
53+
this.socket = io.connect(this.useSandbox ? 'https://socketio.sandbox.cloudconvert.com' : 'https://socketio.cloudconvert.com', {
54+
transports: ['websocket']
55+
});
56+
this.subscribedChannels = new Map<string, boolean>();
57+
}
58+
59+
if (!this.subscribedChannels?.get(channel)) {
60+
this.socket.emit('subscribe', {
61+
channel,
62+
auth: {
63+
headers: {
64+
'Authorization': 'Bearer ' + this.apiKey
65+
}
66+
},
67+
});
68+
this.subscribedChannels?.set(channel, true);
69+
}
70+
71+
this.socket.on(event, function (eventChannel: string, eventData: any): void {
72+
if (channel !== eventChannel) {
73+
return;
74+
}
75+
callback(eventData);
76+
});
77+
78+
}
79+
80+
81+
}
82+
83+

lib/JobsResouce.js

Lines changed: 0 additions & 45 deletions
This file was deleted.

lib/JobsResource.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import CloudConvert from './CloudConvert';
2+
import { Operation, Task, TaskEventData, TaskStatus } from './TasksResource';
3+
4+
export type JobEvent = 'created' | 'updated' | 'finished' | 'failed';
5+
export type JobStatus = 'processing' | 'finished' | 'error';
6+
export type JobTaskStatus = Task['status'] | 'queued'
7+
export interface JobEventData { job: Job }
8+
9+
export interface Job {
10+
id: string;
11+
tag: string | null;
12+
status: TaskStatus;
13+
created_at: string;
14+
started_at: string | null;
15+
ended_at: string | null;
16+
tasks: JobTask[];
17+
}
18+
type NotPresentWhenInsideJob = 'job_id' | 'status'
19+
interface JobTask extends Omit<Task, NotPresentWhenInsideJob> {
20+
name: string;
21+
status: JobTaskStatus;
22+
}
23+
24+
export default class JobsResource {
25+
private readonly cloudConvert: CloudConvert;
26+
27+
constructor(cloudConvert: CloudConvert) {
28+
this.cloudConvert = cloudConvert;
29+
}
30+
31+
async get(id: string, query: null = null): Promise<Job> {
32+
const response = await this.cloudConvert.axios.get('jobs/' + id, {
33+
params: query || {}
34+
});
35+
return response.data.data;
36+
}
37+
38+
async wait(id: string): Promise<Job> {
39+
const response = await this.cloudConvert.axios.get('jobs/' + id + '/wait');
40+
return response.data.data;
41+
}
42+
43+
async all(query: { 'filter[status]'?: JobStatus; 'filter[tag]'?: string; include?: string; per_page?: number; page?: number; } | null = null): Promise<Job[]> {
44+
const response = await this.cloudConvert.axios.get('jobs', {
45+
params: query || {}
46+
});
47+
return response.data.data;
48+
}
49+
50+
// See below for an explanation on how this type signature works
51+
async create(data: JobTemplate | null = null): Promise<Job> {
52+
const response = await this.cloudConvert.axios.post('jobs', data);
53+
return response.data.data;
54+
}
55+
56+
async delete(id: string): Promise<void> {
57+
await this.cloudConvert.axios.delete('jobs/' + id);
58+
}
59+
60+
async subscribeEvent(id: string, event: string, callback: (event: JobEventData) => void): Promise<void> {
61+
this.cloudConvert.subscribe('private-job.' + id, 'job.' + event, callback);
62+
}
63+
64+
async subscribeTaskEvent(id: string, event: string, callback: (event: TaskEventData) => void): Promise<void> {
65+
this.cloudConvert.subscribe('private-job.' + id + '.tasks', 'task.' + event, callback);
66+
}
67+
68+
}
69+
70+
// We need to map the types from the large Operation union type
71+
// to the template syntax from the API specs (confer the README)
72+
// that is used to create a job with a number of tasks. While this
73+
// is possible to write in just two lines of code, we divide this
74+
// up in many small steps in order to explain what's happening:
75+
76+
// All possible operation strings ("import/url" etc)
77+
type PossibleOperationStrings = Operation['operation'];
78+
// Every argument in the tasks object should be assignable to this (for some operation string O)
79+
interface NamedOperation<O> { operation: O }
80+
// Given an operation string O, get the operation for it
81+
type OperationByName<O> = Extract<Operation, NamedOperation<O>>
82+
// Given an operation string O, get the operation data for it
83+
type OperationData<O> = OperationByName<O>['data'];
84+
// Add all properties to task that can only occur in tasks that are inside jobs
85+
interface TaskExtras<O> extends NamedOperation<O> { ignore_error?: boolean; }
86+
// Every argument in the tasks object is typed by this (for some operation string O)
87+
type TaskTemplate<O> = TaskExtras<O> & OperationData<O>;
88+
// Given a union type U of operation strings, turn each operation string into its TaskTemplate
89+
type Distribute<U> = U extends any ? TaskTemplate<U> : never;
90+
// Create a union of all possible tasks
91+
type PossibleOperations = Distribute<PossibleOperationStrings>;
92+
// Allow any number of names, each typed by a possible operation
93+
interface TaskContainer { [name: string]: PossibleOperations; }
94+
// Add the other properties that are required for job creation
95+
interface JobTemplate { tasks: TaskContainer; tag?: string; }

0 commit comments

Comments
 (0)