@planescrm/sdk 0.2.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/README.md +155 -0
- package/dist/index.d.mts +596 -0
- package/dist/index.d.ts +596 -0
- package/dist/index.js +646 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +581 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +36 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
AuthenticationError: () => AuthenticationError,
|
|
34
|
+
AuthorizationError: () => AuthorizationError,
|
|
35
|
+
BaseResource: () => BaseResource,
|
|
36
|
+
Blueprint: () => Blueprint,
|
|
37
|
+
BlueprintClient: () => BlueprintClient,
|
|
38
|
+
BlueprintError: () => BlueprintError,
|
|
39
|
+
CalendarEventResource: () => CalendarEventResource,
|
|
40
|
+
ContractResource: () => ContractResource,
|
|
41
|
+
CustomerResource: () => CustomerResource,
|
|
42
|
+
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
43
|
+
DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
|
|
44
|
+
DEFAULT_TIMEOUT: () => DEFAULT_TIMEOUT,
|
|
45
|
+
EstimateResource: () => EstimateResource,
|
|
46
|
+
InventoryResource: () => InventoryResource,
|
|
47
|
+
InvoiceResource: () => InvoiceResource,
|
|
48
|
+
JobResource: () => JobResource,
|
|
49
|
+
NotFoundError: () => NotFoundError,
|
|
50
|
+
OAuthError: () => OAuthError,
|
|
51
|
+
OpportunityResource: () => OpportunityResource,
|
|
52
|
+
PropertyResource: () => PropertyResource,
|
|
53
|
+
RateLimitError: () => RateLimitError,
|
|
54
|
+
ServerError: () => ServerError,
|
|
55
|
+
ValidationError: () => ValidationError,
|
|
56
|
+
WebhookVerificationError: () => WebhookVerificationError,
|
|
57
|
+
buildAuthorizeUrl: () => buildAuthorizeUrl,
|
|
58
|
+
exchangeCode: () => exchangeCode,
|
|
59
|
+
parseWebhookEvent: () => parseWebhookEvent,
|
|
60
|
+
refreshAccessToken: () => refreshAccessToken,
|
|
61
|
+
verifyWebhookSignature: () => verifyWebhookSignature
|
|
62
|
+
});
|
|
63
|
+
module.exports = __toCommonJS(index_exports);
|
|
64
|
+
|
|
65
|
+
// src/config.ts
|
|
66
|
+
var DEFAULT_BASE_URL = "https://api.planescrm.com/api/v1";
|
|
67
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
68
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
69
|
+
|
|
70
|
+
// src/errors.ts
|
|
71
|
+
var BlueprintError = class extends Error {
|
|
72
|
+
constructor(message, status, code) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = "BlueprintError";
|
|
75
|
+
this.status = status;
|
|
76
|
+
this.code = code;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var AuthenticationError = class extends BlueprintError {
|
|
80
|
+
constructor(message = "Authentication failed") {
|
|
81
|
+
super(message, 401, "authentication_error");
|
|
82
|
+
this.name = "AuthenticationError";
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var AuthorizationError = class extends BlueprintError {
|
|
86
|
+
constructor(message = "Insufficient permissions") {
|
|
87
|
+
super(message, 403, "authorization_error");
|
|
88
|
+
this.name = "AuthorizationError";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var NotFoundError = class extends BlueprintError {
|
|
92
|
+
constructor(message = "Resource not found") {
|
|
93
|
+
super(message, 404, "not_found");
|
|
94
|
+
this.name = "NotFoundError";
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var ValidationError = class extends BlueprintError {
|
|
98
|
+
constructor(message = "Validation failed", fieldErrors = []) {
|
|
99
|
+
super(message, 400, "validation_error");
|
|
100
|
+
this.name = "ValidationError";
|
|
101
|
+
this.fieldErrors = fieldErrors;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var RateLimitError = class extends BlueprintError {
|
|
105
|
+
constructor(retryAfter = 1, message = "Rate limit exceeded") {
|
|
106
|
+
super(message, 429, "rate_limit_error");
|
|
107
|
+
this.name = "RateLimitError";
|
|
108
|
+
this.retryAfter = retryAfter;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var ServerError = class extends BlueprintError {
|
|
112
|
+
constructor(message = "Internal server error") {
|
|
113
|
+
super(message, 500, "server_error");
|
|
114
|
+
this.name = "ServerError";
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var WebhookVerificationError = class extends BlueprintError {
|
|
118
|
+
constructor(message = "Webhook signature verification failed") {
|
|
119
|
+
super(message, 400, "webhook_verification_error");
|
|
120
|
+
this.name = "WebhookVerificationError";
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var OAuthError = class extends BlueprintError {
|
|
124
|
+
constructor(errorCode, message = "OAuth error") {
|
|
125
|
+
super(message, 400, "oauth_error");
|
|
126
|
+
this.name = "OAuthError";
|
|
127
|
+
this.errorCode = errorCode;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/http.ts
|
|
132
|
+
var HttpClient = class {
|
|
133
|
+
constructor(config) {
|
|
134
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
135
|
+
this.accessToken = config.accessToken;
|
|
136
|
+
this.apiKey = config.apiKey;
|
|
137
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
138
|
+
this.retryOnRateLimit = config.retryOnRateLimit !== false;
|
|
139
|
+
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
140
|
+
}
|
|
141
|
+
async request(options) {
|
|
142
|
+
const url = this.buildUrl(options.path, options.params);
|
|
143
|
+
const headers = {
|
|
144
|
+
"Content-Type": "application/json",
|
|
145
|
+
...this.getAuthHeaders(),
|
|
146
|
+
...options.headers
|
|
147
|
+
};
|
|
148
|
+
const fetchOptions = {
|
|
149
|
+
method: options.method,
|
|
150
|
+
headers,
|
|
151
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
152
|
+
};
|
|
153
|
+
if (options.body !== void 0) {
|
|
154
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
155
|
+
}
|
|
156
|
+
let lastError;
|
|
157
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
158
|
+
const response = await fetch(url, fetchOptions);
|
|
159
|
+
if (response.ok) {
|
|
160
|
+
if (response.status === 204) {
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
163
|
+
return await response.json();
|
|
164
|
+
}
|
|
165
|
+
if (response.status === 429 && this.retryOnRateLimit && attempt < this.maxRetries) {
|
|
166
|
+
const retryAfter = this.parseRetryAfter(response);
|
|
167
|
+
await this.sleep(retryAfter * 1e3);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
lastError = await this.mapError(response);
|
|
171
|
+
if (response.status !== 429 || !this.retryOnRateLimit) {
|
|
172
|
+
throw lastError;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
throw lastError ?? new RateLimitError();
|
|
176
|
+
}
|
|
177
|
+
getAuthHeaders() {
|
|
178
|
+
if (this.accessToken) {
|
|
179
|
+
return { Authorization: `Bearer ${this.accessToken}` };
|
|
180
|
+
}
|
|
181
|
+
if (this.apiKey) {
|
|
182
|
+
return { "X-API-Key": this.apiKey };
|
|
183
|
+
}
|
|
184
|
+
return {};
|
|
185
|
+
}
|
|
186
|
+
buildUrl(path, params) {
|
|
187
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
188
|
+
if (params) {
|
|
189
|
+
for (const [key, value] of Object.entries(params)) {
|
|
190
|
+
if (value !== void 0 && value !== null) {
|
|
191
|
+
url.searchParams.set(key, String(value));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return url.toString();
|
|
196
|
+
}
|
|
197
|
+
parseRetryAfter(response) {
|
|
198
|
+
const header = response.headers.get("Retry-After");
|
|
199
|
+
if (header) {
|
|
200
|
+
const seconds = Number(header);
|
|
201
|
+
if (!Number.isNaN(seconds) && seconds > 0) {
|
|
202
|
+
return seconds;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return 1;
|
|
206
|
+
}
|
|
207
|
+
async mapError(response) {
|
|
208
|
+
let body = {};
|
|
209
|
+
try {
|
|
210
|
+
body = await response.json();
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
const message = body["message"] ?? response.statusText ?? "Request failed";
|
|
214
|
+
switch (response.status) {
|
|
215
|
+
case 400: {
|
|
216
|
+
const fieldErrors = body["errors"] ?? [];
|
|
217
|
+
return new ValidationError(message, fieldErrors);
|
|
218
|
+
}
|
|
219
|
+
case 401:
|
|
220
|
+
return new AuthenticationError(message);
|
|
221
|
+
case 403:
|
|
222
|
+
return new AuthorizationError(message);
|
|
223
|
+
case 404:
|
|
224
|
+
return new NotFoundError(message);
|
|
225
|
+
case 429: {
|
|
226
|
+
const retryAfter = this.parseRetryAfter(response);
|
|
227
|
+
return new RateLimitError(retryAfter, message);
|
|
228
|
+
}
|
|
229
|
+
default:
|
|
230
|
+
if (response.status >= 500) {
|
|
231
|
+
return new ServerError(message);
|
|
232
|
+
}
|
|
233
|
+
return new BlueprintError(message, response.status, "unknown_error");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
sleep(ms) {
|
|
237
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// src/resources/base-resource.ts
|
|
242
|
+
var BaseResource = class {
|
|
243
|
+
constructor(http2, basePath) {
|
|
244
|
+
this.http = http2;
|
|
245
|
+
this.basePath = basePath;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* List entities with optional filters and pagination.
|
|
249
|
+
*/
|
|
250
|
+
async list(params) {
|
|
251
|
+
return this.http.request({
|
|
252
|
+
method: "GET",
|
|
253
|
+
path: this.basePath,
|
|
254
|
+
params
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Get a single entity by ID.
|
|
259
|
+
*/
|
|
260
|
+
async get(id) {
|
|
261
|
+
return this.http.request({
|
|
262
|
+
method: "GET",
|
|
263
|
+
path: `${this.basePath}/${id}`
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Create a new entity.
|
|
268
|
+
*/
|
|
269
|
+
async create(data) {
|
|
270
|
+
return this.http.request({
|
|
271
|
+
method: "POST",
|
|
272
|
+
path: this.basePath,
|
|
273
|
+
body: data
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Update an existing entity.
|
|
278
|
+
*/
|
|
279
|
+
async update(id, data) {
|
|
280
|
+
return this.http.request({
|
|
281
|
+
method: "PATCH",
|
|
282
|
+
path: `${this.basePath}/${id}`,
|
|
283
|
+
body: data
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Delete an entity by ID.
|
|
288
|
+
*/
|
|
289
|
+
async delete(id) {
|
|
290
|
+
await this.http.request({
|
|
291
|
+
method: "DELETE",
|
|
292
|
+
path: `${this.basePath}/${id}`
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// src/resources/opportunities.ts
|
|
298
|
+
var OpportunityResource = class extends BaseResource {
|
|
299
|
+
constructor(http2) {
|
|
300
|
+
super(http2, "/opportunities");
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// src/resources/customers.ts
|
|
305
|
+
var CustomerResource = class extends BaseResource {
|
|
306
|
+
constructor(http2) {
|
|
307
|
+
super(http2, "/customers");
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// src/resources/properties.ts
|
|
312
|
+
var PropertyResource = class extends BaseResource {
|
|
313
|
+
constructor(http2) {
|
|
314
|
+
super(http2, "/properties");
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// src/resources/calendar-events.ts
|
|
319
|
+
var CalendarEventResource = class extends BaseResource {
|
|
320
|
+
constructor(http2) {
|
|
321
|
+
super(http2, "/calendar-events");
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// src/resources/contracts.ts
|
|
326
|
+
var ContractResource = class extends BaseResource {
|
|
327
|
+
constructor(http2) {
|
|
328
|
+
super(http2, "/contracts");
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// src/resources/jobs.ts
|
|
333
|
+
var JobResource = class extends BaseResource {
|
|
334
|
+
constructor(http2) {
|
|
335
|
+
super(http2, "/jobs");
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// src/resources/estimates.ts
|
|
340
|
+
var EstimateResource = class extends BaseResource {
|
|
341
|
+
constructor(http2) {
|
|
342
|
+
super(http2, "/estimates");
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// src/resources/invoices.ts
|
|
347
|
+
var InvoiceResource = class extends BaseResource {
|
|
348
|
+
constructor(http2) {
|
|
349
|
+
super(http2, "/invoices");
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// src/resources/inventory.ts
|
|
354
|
+
var InventoryResource = class extends BaseResource {
|
|
355
|
+
constructor(http2) {
|
|
356
|
+
super(http2, "/inventory/items");
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// src/client.ts
|
|
361
|
+
var BlueprintClient = class {
|
|
362
|
+
constructor(config) {
|
|
363
|
+
if (!config.accessToken && !config.apiKey) {
|
|
364
|
+
throw new Error("BlueprintClient requires either accessToken or apiKey");
|
|
365
|
+
}
|
|
366
|
+
if (config.accessToken && config.apiKey) {
|
|
367
|
+
throw new Error("BlueprintClient requires either accessToken or apiKey, not both");
|
|
368
|
+
}
|
|
369
|
+
this.http = new HttpClient(config);
|
|
370
|
+
}
|
|
371
|
+
get opportunities() {
|
|
372
|
+
if (!this._opportunities) {
|
|
373
|
+
this._opportunities = new OpportunityResource(this.http);
|
|
374
|
+
}
|
|
375
|
+
return this._opportunities;
|
|
376
|
+
}
|
|
377
|
+
get customers() {
|
|
378
|
+
if (!this._customers) {
|
|
379
|
+
this._customers = new CustomerResource(this.http);
|
|
380
|
+
}
|
|
381
|
+
return this._customers;
|
|
382
|
+
}
|
|
383
|
+
get properties() {
|
|
384
|
+
if (!this._properties) {
|
|
385
|
+
this._properties = new PropertyResource(this.http);
|
|
386
|
+
}
|
|
387
|
+
return this._properties;
|
|
388
|
+
}
|
|
389
|
+
get calendarEvents() {
|
|
390
|
+
if (!this._calendarEvents) {
|
|
391
|
+
this._calendarEvents = new CalendarEventResource(this.http);
|
|
392
|
+
}
|
|
393
|
+
return this._calendarEvents;
|
|
394
|
+
}
|
|
395
|
+
get contracts() {
|
|
396
|
+
if (!this._contracts) {
|
|
397
|
+
this._contracts = new ContractResource(this.http);
|
|
398
|
+
}
|
|
399
|
+
return this._contracts;
|
|
400
|
+
}
|
|
401
|
+
get jobs() {
|
|
402
|
+
if (!this._jobs) {
|
|
403
|
+
this._jobs = new JobResource(this.http);
|
|
404
|
+
}
|
|
405
|
+
return this._jobs;
|
|
406
|
+
}
|
|
407
|
+
get estimates() {
|
|
408
|
+
if (!this._estimates) {
|
|
409
|
+
this._estimates = new EstimateResource(this.http);
|
|
410
|
+
}
|
|
411
|
+
return this._estimates;
|
|
412
|
+
}
|
|
413
|
+
get invoices() {
|
|
414
|
+
if (!this._invoices) {
|
|
415
|
+
this._invoices = new InvoiceResource(this.http);
|
|
416
|
+
}
|
|
417
|
+
return this._invoices;
|
|
418
|
+
}
|
|
419
|
+
get inventory() {
|
|
420
|
+
if (!this._inventory) {
|
|
421
|
+
this._inventory = new InventoryResource(this.http);
|
|
422
|
+
}
|
|
423
|
+
return this._inventory;
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// src/agent.ts
|
|
428
|
+
var http = __toESM(require("http"));
|
|
429
|
+
var Blueprint = {
|
|
430
|
+
/**
|
|
431
|
+
* Register action handlers and start an HTTP server to receive action requests.
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* ```ts
|
|
435
|
+
* Blueprint.register([
|
|
436
|
+
* {
|
|
437
|
+
* type: 'send_email',
|
|
438
|
+
* async execute(ctx) {
|
|
439
|
+
* // ... send email logic
|
|
440
|
+
* return { status: 'completed', output: { sent: true } };
|
|
441
|
+
* },
|
|
442
|
+
* },
|
|
443
|
+
* ], { port: 3000 });
|
|
444
|
+
* ```
|
|
445
|
+
*/
|
|
446
|
+
register(handlers, options) {
|
|
447
|
+
const port = options?.port ?? 3e3;
|
|
448
|
+
const basePath = options?.path ?? "/actions";
|
|
449
|
+
const onError = options?.onError;
|
|
450
|
+
const handlerMap = /* @__PURE__ */ new Map();
|
|
451
|
+
for (const handler of handlers) {
|
|
452
|
+
handlerMap.set(handler.type, handler);
|
|
453
|
+
}
|
|
454
|
+
const server = http.createServer(async (req, res) => {
|
|
455
|
+
if (req.method !== "POST" || req.url !== basePath) {
|
|
456
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
457
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
try {
|
|
461
|
+
const body = await readBody(req);
|
|
462
|
+
let context;
|
|
463
|
+
try {
|
|
464
|
+
context = JSON.parse(body);
|
|
465
|
+
} catch {
|
|
466
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
467
|
+
res.end(JSON.stringify({ error: "Malformed request body" }));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (!context.action?.type) {
|
|
471
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
472
|
+
res.end(JSON.stringify({ error: "Missing action.type in request body" }));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const handler = handlerMap.get(context.action.type);
|
|
476
|
+
if (!handler) {
|
|
477
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
478
|
+
res.end(
|
|
479
|
+
JSON.stringify({
|
|
480
|
+
error: `No handler registered for action type: ${context.action.type}`
|
|
481
|
+
})
|
|
482
|
+
);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
let result;
|
|
486
|
+
try {
|
|
487
|
+
result = await handler.execute(context);
|
|
488
|
+
} catch (err) {
|
|
489
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown handler error";
|
|
490
|
+
if (onError && err instanceof Error) {
|
|
491
|
+
onError(err);
|
|
492
|
+
}
|
|
493
|
+
result = {
|
|
494
|
+
status: "failed",
|
|
495
|
+
error: errorMessage,
|
|
496
|
+
retryable: false
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
500
|
+
res.end(JSON.stringify(result));
|
|
501
|
+
} catch (err) {
|
|
502
|
+
if (onError && err instanceof Error) {
|
|
503
|
+
onError(err);
|
|
504
|
+
}
|
|
505
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
506
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
server.listen(port);
|
|
510
|
+
const shutdown = () => {
|
|
511
|
+
server.close();
|
|
512
|
+
};
|
|
513
|
+
process.on("SIGTERM", shutdown);
|
|
514
|
+
process.on("SIGINT", shutdown);
|
|
515
|
+
return server;
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
function readBody(req) {
|
|
519
|
+
return new Promise((resolve, reject) => {
|
|
520
|
+
const chunks = [];
|
|
521
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
522
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString()));
|
|
523
|
+
req.on("error", reject);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/webhooks.ts
|
|
528
|
+
var import_node_crypto = require("crypto");
|
|
529
|
+
function verifyWebhookSignature(payload, signature, secret) {
|
|
530
|
+
if (!signature || !secret || !payload) {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
const prefix = "sha256=";
|
|
534
|
+
if (!signature.startsWith(prefix)) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
const receivedHex = signature.slice(prefix.length);
|
|
538
|
+
if (!receivedHex) {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
const expectedHmac = (0, import_node_crypto.createHmac)("sha256", secret).update(payload).digest("hex");
|
|
542
|
+
const receivedBuf = Buffer.from(receivedHex, "hex");
|
|
543
|
+
const expectedBuf = Buffer.from(expectedHmac, "hex");
|
|
544
|
+
if (receivedBuf.length !== expectedBuf.length) {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
return (0, import_node_crypto.timingSafeEqual)(receivedBuf, expectedBuf);
|
|
548
|
+
}
|
|
549
|
+
function parseWebhookEvent(body, signature, secret) {
|
|
550
|
+
if (!verifyWebhookSignature(body, signature, secret)) {
|
|
551
|
+
throw new WebhookVerificationError();
|
|
552
|
+
}
|
|
553
|
+
return JSON.parse(body);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/oauth.ts
|
|
557
|
+
var import_node_crypto2 = require("crypto");
|
|
558
|
+
function buildAuthorizeUrl(options) {
|
|
559
|
+
const state = options.state ?? (0, import_node_crypto2.randomBytes)(16).toString("hex");
|
|
560
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
561
|
+
const url = new URL(`${baseUrl}/oauth/authorize`);
|
|
562
|
+
url.searchParams.set("client_id", options.clientId);
|
|
563
|
+
url.searchParams.set("redirect_uri", options.redirectUri);
|
|
564
|
+
url.searchParams.set("scope", options.scopes.join(" "));
|
|
565
|
+
url.searchParams.set("response_type", "code");
|
|
566
|
+
url.searchParams.set("state", state);
|
|
567
|
+
return url.toString();
|
|
568
|
+
}
|
|
569
|
+
async function exchangeCode(options) {
|
|
570
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
571
|
+
const url = `${baseUrl}/oauth/token`;
|
|
572
|
+
const body = new URLSearchParams({
|
|
573
|
+
grant_type: "authorization_code",
|
|
574
|
+
client_id: options.clientId,
|
|
575
|
+
client_secret: options.clientSecret,
|
|
576
|
+
code: options.code,
|
|
577
|
+
redirect_uri: options.redirectUri
|
|
578
|
+
});
|
|
579
|
+
const response = await fetch(url, {
|
|
580
|
+
method: "POST",
|
|
581
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
582
|
+
body: body.toString()
|
|
583
|
+
});
|
|
584
|
+
if (!response.ok) {
|
|
585
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
586
|
+
const errorCode = errorBody["error"] ?? "unknown_error";
|
|
587
|
+
const errorDescription = errorBody["error_description"] ?? "Token exchange failed";
|
|
588
|
+
throw new OAuthError(errorCode, errorDescription);
|
|
589
|
+
}
|
|
590
|
+
return await response.json();
|
|
591
|
+
}
|
|
592
|
+
async function refreshAccessToken(options) {
|
|
593
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
594
|
+
const url = `${baseUrl}/oauth/token`;
|
|
595
|
+
const body = new URLSearchParams({
|
|
596
|
+
grant_type: "refresh_token",
|
|
597
|
+
client_id: options.clientId,
|
|
598
|
+
client_secret: options.clientSecret,
|
|
599
|
+
refresh_token: options.refreshToken
|
|
600
|
+
});
|
|
601
|
+
const response = await fetch(url, {
|
|
602
|
+
method: "POST",
|
|
603
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
604
|
+
body: body.toString()
|
|
605
|
+
});
|
|
606
|
+
if (!response.ok) {
|
|
607
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
608
|
+
const errorCode = errorBody["error"] ?? "unknown_error";
|
|
609
|
+
const errorDescription = errorBody["error_description"] ?? "Token refresh failed";
|
|
610
|
+
throw new OAuthError(errorCode, errorDescription);
|
|
611
|
+
}
|
|
612
|
+
return await response.json();
|
|
613
|
+
}
|
|
614
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
615
|
+
0 && (module.exports = {
|
|
616
|
+
AuthenticationError,
|
|
617
|
+
AuthorizationError,
|
|
618
|
+
BaseResource,
|
|
619
|
+
Blueprint,
|
|
620
|
+
BlueprintClient,
|
|
621
|
+
BlueprintError,
|
|
622
|
+
CalendarEventResource,
|
|
623
|
+
ContractResource,
|
|
624
|
+
CustomerResource,
|
|
625
|
+
DEFAULT_BASE_URL,
|
|
626
|
+
DEFAULT_MAX_RETRIES,
|
|
627
|
+
DEFAULT_TIMEOUT,
|
|
628
|
+
EstimateResource,
|
|
629
|
+
InventoryResource,
|
|
630
|
+
InvoiceResource,
|
|
631
|
+
JobResource,
|
|
632
|
+
NotFoundError,
|
|
633
|
+
OAuthError,
|
|
634
|
+
OpportunityResource,
|
|
635
|
+
PropertyResource,
|
|
636
|
+
RateLimitError,
|
|
637
|
+
ServerError,
|
|
638
|
+
ValidationError,
|
|
639
|
+
WebhookVerificationError,
|
|
640
|
+
buildAuthorizeUrl,
|
|
641
|
+
exchangeCode,
|
|
642
|
+
parseWebhookEvent,
|
|
643
|
+
refreshAccessToken,
|
|
644
|
+
verifyWebhookSignature
|
|
645
|
+
});
|
|
646
|
+
//# sourceMappingURL=index.js.map
|