@planescrm/sdk 0.2.0 → 0.4.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/dist/index.d.mts +685 -106
- package/dist/index.d.ts +685 -106
- package/dist/index.js +580 -190
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +567 -187
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -30,31 +30,41 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
ActivityResource: () => ActivityResource,
|
|
34
|
+
AgentApp: () => AgentApp,
|
|
33
35
|
AuthenticationError: () => AuthenticationError,
|
|
34
36
|
AuthorizationError: () => AuthorizationError,
|
|
35
37
|
BaseResource: () => BaseResource,
|
|
36
|
-
Blueprint: () => Blueprint,
|
|
37
|
-
BlueprintClient: () => BlueprintClient,
|
|
38
|
-
BlueprintError: () => BlueprintError,
|
|
39
38
|
CalendarEventResource: () => CalendarEventResource,
|
|
39
|
+
ContactChannelResource: () => ContactChannelResource,
|
|
40
|
+
ContactResource: () => ContactResource,
|
|
40
41
|
ContractResource: () => ContractResource,
|
|
41
42
|
CustomerResource: () => CustomerResource,
|
|
42
43
|
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
43
44
|
DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
|
|
44
45
|
DEFAULT_TIMEOUT: () => DEFAULT_TIMEOUT,
|
|
46
|
+
DEFAULT_TOKEN_REFRESH_SKEW_MS: () => DEFAULT_TOKEN_REFRESH_SKEW_MS,
|
|
45
47
|
EstimateResource: () => EstimateResource,
|
|
46
48
|
InventoryResource: () => InventoryResource,
|
|
47
49
|
InvoiceResource: () => InvoiceResource,
|
|
48
50
|
JobResource: () => JobResource,
|
|
51
|
+
MemoryTokenStorage: () => MemoryTokenStorage,
|
|
49
52
|
NotFoundError: () => NotFoundError,
|
|
50
53
|
OAuthError: () => OAuthError,
|
|
51
54
|
OpportunityResource: () => OpportunityResource,
|
|
55
|
+
Planes: () => Planes,
|
|
56
|
+
PlanesClient: () => PlanesClient,
|
|
57
|
+
PlanesError: () => PlanesError,
|
|
52
58
|
PropertyResource: () => PropertyResource,
|
|
53
59
|
RateLimitError: () => RateLimitError,
|
|
54
60
|
ServerError: () => ServerError,
|
|
61
|
+
TagResource: () => TagResource,
|
|
55
62
|
ValidationError: () => ValidationError,
|
|
63
|
+
WebhookRouter: () => WebhookRouter,
|
|
56
64
|
WebhookVerificationError: () => WebhookVerificationError,
|
|
57
65
|
buildAuthorizeUrl: () => buildAuthorizeUrl,
|
|
66
|
+
clientCredentialsGrant: () => clientCredentialsGrant,
|
|
67
|
+
createAgentApp: () => createAgentApp,
|
|
58
68
|
exchangeCode: () => exchangeCode,
|
|
59
69
|
parseWebhookEvent: () => parseWebhookEvent,
|
|
60
70
|
refreshAccessToken: () => refreshAccessToken,
|
|
@@ -63,64 +73,76 @@ __export(index_exports, {
|
|
|
63
73
|
module.exports = __toCommonJS(index_exports);
|
|
64
74
|
|
|
65
75
|
// src/config.ts
|
|
76
|
+
var MemoryTokenStorage = class {
|
|
77
|
+
constructor(seed) {
|
|
78
|
+
this.tokens = seed ?? null;
|
|
79
|
+
}
|
|
80
|
+
async getTokens() {
|
|
81
|
+
return this.tokens;
|
|
82
|
+
}
|
|
83
|
+
async setTokens(tokens) {
|
|
84
|
+
this.tokens = tokens;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
66
87
|
var DEFAULT_BASE_URL = "https://api.planescrm.com/api/v1";
|
|
67
88
|
var DEFAULT_TIMEOUT = 3e4;
|
|
68
89
|
var DEFAULT_MAX_RETRIES = 3;
|
|
90
|
+
var DEFAULT_TOKEN_REFRESH_SKEW_MS = 6e4;
|
|
69
91
|
|
|
70
92
|
// src/errors.ts
|
|
71
|
-
var
|
|
93
|
+
var PlanesError = class extends Error {
|
|
72
94
|
constructor(message, status, code) {
|
|
73
95
|
super(message);
|
|
74
|
-
this.name = "
|
|
96
|
+
this.name = "PlanesError";
|
|
75
97
|
this.status = status;
|
|
76
98
|
this.code = code;
|
|
77
99
|
}
|
|
78
100
|
};
|
|
79
|
-
var AuthenticationError = class extends
|
|
101
|
+
var AuthenticationError = class extends PlanesError {
|
|
80
102
|
constructor(message = "Authentication failed") {
|
|
81
103
|
super(message, 401, "authentication_error");
|
|
82
104
|
this.name = "AuthenticationError";
|
|
83
105
|
}
|
|
84
106
|
};
|
|
85
|
-
var AuthorizationError = class extends
|
|
107
|
+
var AuthorizationError = class extends PlanesError {
|
|
86
108
|
constructor(message = "Insufficient permissions") {
|
|
87
109
|
super(message, 403, "authorization_error");
|
|
88
110
|
this.name = "AuthorizationError";
|
|
89
111
|
}
|
|
90
112
|
};
|
|
91
|
-
var NotFoundError = class extends
|
|
113
|
+
var NotFoundError = class extends PlanesError {
|
|
92
114
|
constructor(message = "Resource not found") {
|
|
93
115
|
super(message, 404, "not_found");
|
|
94
116
|
this.name = "NotFoundError";
|
|
95
117
|
}
|
|
96
118
|
};
|
|
97
|
-
var ValidationError = class extends
|
|
119
|
+
var ValidationError = class extends PlanesError {
|
|
98
120
|
constructor(message = "Validation failed", fieldErrors = []) {
|
|
99
121
|
super(message, 400, "validation_error");
|
|
100
122
|
this.name = "ValidationError";
|
|
101
123
|
this.fieldErrors = fieldErrors;
|
|
102
124
|
}
|
|
103
125
|
};
|
|
104
|
-
var RateLimitError = class extends
|
|
126
|
+
var RateLimitError = class extends PlanesError {
|
|
105
127
|
constructor(retryAfter = 1, message = "Rate limit exceeded") {
|
|
106
128
|
super(message, 429, "rate_limit_error");
|
|
107
129
|
this.name = "RateLimitError";
|
|
108
130
|
this.retryAfter = retryAfter;
|
|
109
131
|
}
|
|
110
132
|
};
|
|
111
|
-
var ServerError = class extends
|
|
133
|
+
var ServerError = class extends PlanesError {
|
|
112
134
|
constructor(message = "Internal server error") {
|
|
113
135
|
super(message, 500, "server_error");
|
|
114
136
|
this.name = "ServerError";
|
|
115
137
|
}
|
|
116
138
|
};
|
|
117
|
-
var WebhookVerificationError = class extends
|
|
139
|
+
var WebhookVerificationError = class extends PlanesError {
|
|
118
140
|
constructor(message = "Webhook signature verification failed") {
|
|
119
141
|
super(message, 400, "webhook_verification_error");
|
|
120
142
|
this.name = "WebhookVerificationError";
|
|
121
143
|
}
|
|
122
144
|
};
|
|
123
|
-
var OAuthError = class extends
|
|
145
|
+
var OAuthError = class extends PlanesError {
|
|
124
146
|
constructor(errorCode, message = "OAuth error") {
|
|
125
147
|
super(message, 400, "oauth_error");
|
|
126
148
|
this.name = "OAuthError";
|
|
@@ -128,6 +150,87 @@ var OAuthError = class extends BlueprintError {
|
|
|
128
150
|
}
|
|
129
151
|
};
|
|
130
152
|
|
|
153
|
+
// src/oauth.ts
|
|
154
|
+
var import_node_crypto = require("crypto");
|
|
155
|
+
function buildAuthorizeUrl(options) {
|
|
156
|
+
const state = options.state ?? (0, import_node_crypto.randomBytes)(16).toString("hex");
|
|
157
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
158
|
+
const url = new URL(`${baseUrl}/oauth/authorize`);
|
|
159
|
+
url.searchParams.set("client_id", options.clientId);
|
|
160
|
+
url.searchParams.set("redirect_uri", options.redirectUri);
|
|
161
|
+
url.searchParams.set("scope", options.scopes.join(" "));
|
|
162
|
+
url.searchParams.set("response_type", "code");
|
|
163
|
+
url.searchParams.set("state", state);
|
|
164
|
+
return url.toString();
|
|
165
|
+
}
|
|
166
|
+
async function exchangeCode(options) {
|
|
167
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
168
|
+
const url = `${baseUrl}/oauth/token`;
|
|
169
|
+
const body = new URLSearchParams({
|
|
170
|
+
grant_type: "authorization_code",
|
|
171
|
+
client_id: options.clientId,
|
|
172
|
+
client_secret: options.clientSecret,
|
|
173
|
+
code: options.code,
|
|
174
|
+
redirect_uri: options.redirectUri
|
|
175
|
+
});
|
|
176
|
+
const response = await fetch(url, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
179
|
+
body: body.toString()
|
|
180
|
+
});
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
183
|
+
const errorCode = errorBody["error"] ?? "unknown_error";
|
|
184
|
+
const errorDescription = errorBody["error_description"] ?? "Token exchange failed";
|
|
185
|
+
throw new OAuthError(errorCode, errorDescription);
|
|
186
|
+
}
|
|
187
|
+
return await response.json();
|
|
188
|
+
}
|
|
189
|
+
async function refreshAccessToken(options) {
|
|
190
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
191
|
+
const url = `${baseUrl}/oauth/token`;
|
|
192
|
+
const body = new URLSearchParams({
|
|
193
|
+
grant_type: "refresh_token",
|
|
194
|
+
client_id: options.clientId,
|
|
195
|
+
client_secret: options.clientSecret,
|
|
196
|
+
refresh_token: options.refreshToken
|
|
197
|
+
});
|
|
198
|
+
const response = await fetch(url, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
201
|
+
body: body.toString()
|
|
202
|
+
});
|
|
203
|
+
if (!response.ok) {
|
|
204
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
205
|
+
const errorCode = errorBody["error"] ?? "unknown_error";
|
|
206
|
+
const errorDescription = errorBody["error_description"] ?? "Token refresh failed";
|
|
207
|
+
throw new OAuthError(errorCode, errorDescription);
|
|
208
|
+
}
|
|
209
|
+
return await response.json();
|
|
210
|
+
}
|
|
211
|
+
async function clientCredentialsGrant(options) {
|
|
212
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
213
|
+
const url = `${baseUrl}/oauth/token`;
|
|
214
|
+
const body = new URLSearchParams({
|
|
215
|
+
grant_type: "client_credentials",
|
|
216
|
+
client_id: options.clientId,
|
|
217
|
+
client_secret: options.clientSecret,
|
|
218
|
+
scope: options.scopes.join(" ")
|
|
219
|
+
});
|
|
220
|
+
const response = await fetch(url, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
223
|
+
body: body.toString()
|
|
224
|
+
});
|
|
225
|
+
if (!response.ok) {
|
|
226
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
227
|
+
const errorCode = errorBody["error"] ?? "unknown_error";
|
|
228
|
+
const errorDescription = errorBody["error_description"] ?? "Client credentials grant failed";
|
|
229
|
+
throw new OAuthError(errorCode, errorDescription);
|
|
230
|
+
}
|
|
231
|
+
return await response.json();
|
|
232
|
+
}
|
|
233
|
+
|
|
131
234
|
// src/http.ts
|
|
132
235
|
var HttpClient = class {
|
|
133
236
|
constructor(config) {
|
|
@@ -137,24 +240,35 @@ var HttpClient = class {
|
|
|
137
240
|
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
138
241
|
this.retryOnRateLimit = config.retryOnRateLimit !== false;
|
|
139
242
|
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
243
|
+
if (config.clientId && config.clientSecret) {
|
|
244
|
+
this.oauth = {
|
|
245
|
+
clientId: config.clientId,
|
|
246
|
+
clientSecret: config.clientSecret,
|
|
247
|
+
tokenStorage: config.tokenStorage ?? new MemoryTokenStorage(),
|
|
248
|
+
skewMs: config.tokenRefreshSkewMs ?? DEFAULT_TOKEN_REFRESH_SKEW_MS,
|
|
249
|
+
scopes: config.scopes
|
|
250
|
+
};
|
|
251
|
+
}
|
|
140
252
|
}
|
|
141
253
|
async request(options) {
|
|
142
254
|
const url = this.buildUrl(options.path, options.params);
|
|
143
|
-
const
|
|
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
|
-
}
|
|
255
|
+
const bodyStr = options.body !== void 0 ? JSON.stringify(options.body) : void 0;
|
|
156
256
|
let lastError;
|
|
257
|
+
let didRefreshOn401 = false;
|
|
157
258
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
259
|
+
const headers = {
|
|
260
|
+
"Content-Type": "application/json",
|
|
261
|
+
...await this.resolveAuthHeaders(),
|
|
262
|
+
...options.headers
|
|
263
|
+
};
|
|
264
|
+
const fetchOptions = {
|
|
265
|
+
method: options.method,
|
|
266
|
+
headers,
|
|
267
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
268
|
+
};
|
|
269
|
+
if (bodyStr !== void 0) {
|
|
270
|
+
fetchOptions.body = bodyStr;
|
|
271
|
+
}
|
|
158
272
|
const response = await fetch(url, fetchOptions);
|
|
159
273
|
if (response.ok) {
|
|
160
274
|
if (response.status === 204) {
|
|
@@ -167,6 +281,12 @@ var HttpClient = class {
|
|
|
167
281
|
await this.sleep(retryAfter * 1e3);
|
|
168
282
|
continue;
|
|
169
283
|
}
|
|
284
|
+
if (response.status === 401 && this.oauth && !didRefreshOn401) {
|
|
285
|
+
didRefreshOn401 = true;
|
|
286
|
+
await this.refreshTokens();
|
|
287
|
+
attempt--;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
170
290
|
lastError = await this.mapError(response);
|
|
171
291
|
if (response.status !== 429 || !this.retryOnRateLimit) {
|
|
172
292
|
throw lastError;
|
|
@@ -183,6 +303,76 @@ var HttpClient = class {
|
|
|
183
303
|
}
|
|
184
304
|
return {};
|
|
185
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Resolve the Authorization header for the current request. For the static
|
|
308
|
+
* accessToken/apiKey modes this is {@link getAuthHeaders}. For the
|
|
309
|
+
* OAuth-managed path it reads the stored token, proactively refreshing when
|
|
310
|
+
* the token is within the skew window of expiry.
|
|
311
|
+
*/
|
|
312
|
+
async resolveAuthHeaders() {
|
|
313
|
+
if (!this.oauth) {
|
|
314
|
+
return this.getAuthHeaders();
|
|
315
|
+
}
|
|
316
|
+
const tokens = await this.oauth.tokenStorage.getTokens();
|
|
317
|
+
if (!tokens) {
|
|
318
|
+
return {};
|
|
319
|
+
}
|
|
320
|
+
if (tokens.expiresAt - Date.now() <= this.oauth.skewMs) {
|
|
321
|
+
const refreshed = await this.refreshTokens();
|
|
322
|
+
return { Authorization: `Bearer ${refreshed.accessToken}` };
|
|
323
|
+
}
|
|
324
|
+
return { Authorization: `Bearer ${tokens.accessToken}` };
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Refresh the OAuth access token via `refreshAccessToken` and persist the new
|
|
328
|
+
* tokens through the configured {@link TokenStorage}. Concurrent callers share
|
|
329
|
+
* a single in-flight refresh.
|
|
330
|
+
*/
|
|
331
|
+
async refreshTokens() {
|
|
332
|
+
if (!this.oauth) {
|
|
333
|
+
throw new AuthenticationError("Token refresh requires the OAuth clientId/clientSecret path");
|
|
334
|
+
}
|
|
335
|
+
if (this.refreshInFlight) {
|
|
336
|
+
return this.refreshInFlight;
|
|
337
|
+
}
|
|
338
|
+
const oauth = this.oauth;
|
|
339
|
+
this.refreshInFlight = (async () => {
|
|
340
|
+
const current = await oauth.tokenStorage.getTokens();
|
|
341
|
+
if (!current?.refreshToken) {
|
|
342
|
+
const response2 = await clientCredentialsGrant({
|
|
343
|
+
baseUrl: this.baseUrl,
|
|
344
|
+
clientId: oauth.clientId,
|
|
345
|
+
clientSecret: oauth.clientSecret,
|
|
346
|
+
scopes: oauth.scopes ?? []
|
|
347
|
+
});
|
|
348
|
+
const next2 = {
|
|
349
|
+
accessToken: response2.access_token,
|
|
350
|
+
refreshToken: response2.refresh_token ?? null,
|
|
351
|
+
expiresAt: Date.now() + response2.expires_in * 1e3
|
|
352
|
+
};
|
|
353
|
+
await oauth.tokenStorage.setTokens(next2);
|
|
354
|
+
return next2;
|
|
355
|
+
}
|
|
356
|
+
const response = await refreshAccessToken({
|
|
357
|
+
baseUrl: this.baseUrl,
|
|
358
|
+
clientId: oauth.clientId,
|
|
359
|
+
clientSecret: oauth.clientSecret,
|
|
360
|
+
refreshToken: current.refreshToken
|
|
361
|
+
});
|
|
362
|
+
const next = {
|
|
363
|
+
accessToken: response.access_token,
|
|
364
|
+
refreshToken: response.refresh_token || current.refreshToken,
|
|
365
|
+
expiresAt: Date.now() + response.expires_in * 1e3
|
|
366
|
+
};
|
|
367
|
+
await oauth.tokenStorage.setTokens(next);
|
|
368
|
+
return next;
|
|
369
|
+
})();
|
|
370
|
+
try {
|
|
371
|
+
return await this.refreshInFlight;
|
|
372
|
+
} finally {
|
|
373
|
+
this.refreshInFlight = void 0;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
186
376
|
buildUrl(path, params) {
|
|
187
377
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
188
378
|
if (params) {
|
|
@@ -230,7 +420,7 @@ var HttpClient = class {
|
|
|
230
420
|
if (response.status >= 500) {
|
|
231
421
|
return new ServerError(message);
|
|
232
422
|
}
|
|
233
|
-
return new
|
|
423
|
+
return new PlanesError(message, response.status, "unknown_error");
|
|
234
424
|
}
|
|
235
425
|
}
|
|
236
426
|
sleep(ms) {
|
|
@@ -357,14 +547,207 @@ var InventoryResource = class extends BaseResource {
|
|
|
357
547
|
}
|
|
358
548
|
};
|
|
359
549
|
|
|
550
|
+
// src/resources/contacts.ts
|
|
551
|
+
var ContactChannelResource = class {
|
|
552
|
+
constructor(http2, contactId) {
|
|
553
|
+
this.http = http2;
|
|
554
|
+
this.basePath = `/contacts/${contactId}/channels`;
|
|
555
|
+
}
|
|
556
|
+
/** Returns `{ channel, duplicateOf }` — `duplicateOf` is the dedup signal. */
|
|
557
|
+
async create(data) {
|
|
558
|
+
return this.http.request({
|
|
559
|
+
method: "POST",
|
|
560
|
+
path: this.basePath,
|
|
561
|
+
body: data
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
async update(channelId, data) {
|
|
565
|
+
return this.http.request({
|
|
566
|
+
method: "PATCH",
|
|
567
|
+
path: `${this.basePath}/${channelId}`,
|
|
568
|
+
body: data
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
async delete(channelId) {
|
|
572
|
+
await this.http.request({
|
|
573
|
+
method: "DELETE",
|
|
574
|
+
path: `${this.basePath}/${channelId}`
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
var ContactResource = class extends BaseResource {
|
|
579
|
+
constructor(http2) {
|
|
580
|
+
super(http2, "/contacts");
|
|
581
|
+
}
|
|
582
|
+
async merge(winnerId, loserId) {
|
|
583
|
+
return this.http.request({
|
|
584
|
+
method: "POST",
|
|
585
|
+
path: `/contacts/${winnerId}/merge/${loserId}`
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
channels(contactId) {
|
|
589
|
+
return new ContactChannelResource(this.http, contactId);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// src/resources/activities.ts
|
|
594
|
+
var ActivityResource = class {
|
|
595
|
+
constructor(http2) {
|
|
596
|
+
this.http = http2;
|
|
597
|
+
}
|
|
598
|
+
async create(data) {
|
|
599
|
+
return this.http.request({
|
|
600
|
+
method: "POST",
|
|
601
|
+
path: "/activities",
|
|
602
|
+
body: data
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
async list(params) {
|
|
606
|
+
return this.http.request({
|
|
607
|
+
method: "GET",
|
|
608
|
+
path: "/activities",
|
|
609
|
+
params
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
async listForEntity(entityType, entityId, params) {
|
|
613
|
+
return this.http.request({
|
|
614
|
+
method: "GET",
|
|
615
|
+
path: `/${entityType}/${entityId}/activities`,
|
|
616
|
+
params
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// src/resources/tags.ts
|
|
622
|
+
var TagResource = class extends BaseResource {
|
|
623
|
+
constructor(http2) {
|
|
624
|
+
super(http2, "/tags");
|
|
625
|
+
}
|
|
626
|
+
/** `TagsService.setEntityTags()` returns the final tag set wrapped in `{ tags }`. */
|
|
627
|
+
async setEntityTags(entityType, entityId, data) {
|
|
628
|
+
return this.http.request({
|
|
629
|
+
method: "PUT",
|
|
630
|
+
path: `/entities/${entityType}/${entityId}/tags`,
|
|
631
|
+
body: data
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
async getEntityTags(entityType, entityId) {
|
|
635
|
+
return this.http.request({
|
|
636
|
+
method: "GET",
|
|
637
|
+
path: `/entities/${entityType}/${entityId}/tags`
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
async getEntities(tagId, params) {
|
|
641
|
+
return this.http.request({
|
|
642
|
+
method: "GET",
|
|
643
|
+
path: `/tags/${tagId}/entities`,
|
|
644
|
+
params
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// src/webhooks.ts
|
|
650
|
+
var import_node_crypto2 = require("crypto");
|
|
651
|
+
function verifyWebhookSignature(payload, signature, secret) {
|
|
652
|
+
if (!signature || !secret || !payload) {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
const prefix = "sha256=";
|
|
656
|
+
if (!signature.startsWith(prefix)) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
const receivedHex = signature.slice(prefix.length);
|
|
660
|
+
if (!receivedHex) {
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
const expectedHmac = (0, import_node_crypto2.createHmac)("sha256", secret).update(payload).digest("hex");
|
|
664
|
+
const receivedBuf = Buffer.from(receivedHex, "hex");
|
|
665
|
+
const expectedBuf = Buffer.from(expectedHmac, "hex");
|
|
666
|
+
if (receivedBuf.length !== expectedBuf.length) {
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
return (0, import_node_crypto2.timingSafeEqual)(receivedBuf, expectedBuf);
|
|
670
|
+
}
|
|
671
|
+
function parseWebhookEvent(body, signature, secret) {
|
|
672
|
+
if (!verifyWebhookSignature(body, signature, secret)) {
|
|
673
|
+
throw new WebhookVerificationError();
|
|
674
|
+
}
|
|
675
|
+
return JSON.parse(body);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/webhook-router.ts
|
|
679
|
+
var WebhookRouter = class {
|
|
680
|
+
constructor() {
|
|
681
|
+
/** pattern (composite key / entity wildcard / '*') → registered handlers. */
|
|
682
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Register a handler for an event pattern.
|
|
686
|
+
*
|
|
687
|
+
* @param pattern - `"{entity_type}.{event_type}"` (e.g. `opportunity.created`),
|
|
688
|
+
* an entity wildcard `"{entity_type}.*"` (e.g. `opportunity.*`), or the
|
|
689
|
+
* global wildcard `"*"`. Matching is exact-string against the derived key;
|
|
690
|
+
* see the class docs for the dotted-`event_type` shape.
|
|
691
|
+
* @param handler - Invoked with the full {@link WebhookDelivery} on a match.
|
|
692
|
+
* @returns `this`, for chaining.
|
|
693
|
+
*/
|
|
694
|
+
on(pattern, handler) {
|
|
695
|
+
const existing = this.handlers.get(pattern);
|
|
696
|
+
if (existing) {
|
|
697
|
+
existing.push(handler);
|
|
698
|
+
} else {
|
|
699
|
+
this.handlers.set(pattern, [handler]);
|
|
700
|
+
}
|
|
701
|
+
return this;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Verify, parse, and dispatch a webhook delivery.
|
|
705
|
+
*
|
|
706
|
+
* The signature is verified first: on failure this throws
|
|
707
|
+
* {@link WebhookVerificationError} and dispatches nothing. On success it
|
|
708
|
+
* dispatches to every matching handler — specific key, then entity wildcard,
|
|
709
|
+
* then global — awaiting async handlers before resolving.
|
|
710
|
+
*
|
|
711
|
+
* @param input - The raw `{ body, signature, secret }`.
|
|
712
|
+
* @returns The parsed {@link WebhookDelivery}.
|
|
713
|
+
* @throws WebhookVerificationError if the signature is invalid.
|
|
714
|
+
*/
|
|
715
|
+
async process(input) {
|
|
716
|
+
const delivery = parseWebhookEvent(input.body, input.signature, input.secret);
|
|
717
|
+
const { entity_type, event_type } = delivery.event;
|
|
718
|
+
const patterns = [`${entity_type}.${event_type}`, `${entity_type}.*`, "*"];
|
|
719
|
+
for (const pattern of patterns) {
|
|
720
|
+
const matched = this.handlers.get(pattern);
|
|
721
|
+
if (!matched) continue;
|
|
722
|
+
for (const handler of matched) {
|
|
723
|
+
await handler(delivery);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return delivery;
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
|
|
360
730
|
// src/client.ts
|
|
361
|
-
var
|
|
731
|
+
var PlanesClient = class {
|
|
362
732
|
constructor(config) {
|
|
363
|
-
|
|
364
|
-
|
|
733
|
+
const hasOAuthCreds = Boolean(config.clientId) || Boolean(config.clientSecret);
|
|
734
|
+
const modes = [
|
|
735
|
+
config.accessToken ? "accessToken" : null,
|
|
736
|
+
config.apiKey ? "apiKey" : null,
|
|
737
|
+
hasOAuthCreds ? "clientId+clientSecret" : null
|
|
738
|
+
].filter((m) => m !== null);
|
|
739
|
+
if (modes.length === 0) {
|
|
740
|
+
throw new Error(
|
|
741
|
+
"PlanesClient requires one auth mode: accessToken, apiKey, or clientId + clientSecret"
|
|
742
|
+
);
|
|
365
743
|
}
|
|
366
|
-
if (
|
|
367
|
-
throw new Error(
|
|
744
|
+
if (modes.length > 1) {
|
|
745
|
+
throw new Error(
|
|
746
|
+
`PlanesClient requires exactly one auth mode, not multiple (got: ${modes.join(", ")})`
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
if (hasOAuthCreds && (!config.clientId || !config.clientSecret)) {
|
|
750
|
+
throw new Error("PlanesClient OAuth mode requires both clientId and clientSecret");
|
|
368
751
|
}
|
|
369
752
|
this.http = new HttpClient(config);
|
|
370
753
|
}
|
|
@@ -422,17 +805,134 @@ var BlueprintClient = class {
|
|
|
422
805
|
}
|
|
423
806
|
return this._inventory;
|
|
424
807
|
}
|
|
808
|
+
get contacts() {
|
|
809
|
+
if (!this._contacts) {
|
|
810
|
+
this._contacts = new ContactResource(this.http);
|
|
811
|
+
}
|
|
812
|
+
return this._contacts;
|
|
813
|
+
}
|
|
814
|
+
get activities() {
|
|
815
|
+
if (!this._activities) {
|
|
816
|
+
this._activities = new ActivityResource(this.http);
|
|
817
|
+
}
|
|
818
|
+
return this._activities;
|
|
819
|
+
}
|
|
820
|
+
get tags() {
|
|
821
|
+
if (!this._tags) {
|
|
822
|
+
this._tags = new TagResource(this.http);
|
|
823
|
+
}
|
|
824
|
+
return this._tags;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Ergonomic webhook event router — `client.webhooks.on(pattern, handler)`
|
|
828
|
+
* plus `client.webhooks.process({ body, signature, secret })`. Auth-independent
|
|
829
|
+
* (the signing secret is supplied per `.process()` call), so it is created
|
|
830
|
+
* lazily without the HTTP client. See {@link WebhookRouter}.
|
|
831
|
+
*/
|
|
832
|
+
get webhooks() {
|
|
833
|
+
if (!this._webhooks) {
|
|
834
|
+
this._webhooks = new WebhookRouter();
|
|
835
|
+
}
|
|
836
|
+
return this._webhooks;
|
|
837
|
+
}
|
|
425
838
|
};
|
|
426
839
|
|
|
427
840
|
// src/agent.ts
|
|
428
841
|
var http = __toESM(require("http"));
|
|
429
|
-
|
|
842
|
+
function createActionServer(handlerMap, options) {
|
|
843
|
+
const port = options?.port ?? 3e3;
|
|
844
|
+
const basePath = options?.path ?? "/actions";
|
|
845
|
+
const onError = options?.onError;
|
|
846
|
+
const server = http.createServer(async (req, res) => {
|
|
847
|
+
if (req.method !== "POST" || req.url !== basePath) {
|
|
848
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
849
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
try {
|
|
853
|
+
const body = await readBody(req);
|
|
854
|
+
let envelope;
|
|
855
|
+
try {
|
|
856
|
+
envelope = JSON.parse(body);
|
|
857
|
+
} catch {
|
|
858
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
859
|
+
res.end(JSON.stringify({ error: "Malformed request body" }));
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const wire = envelope?.action_context;
|
|
863
|
+
const actionType = wire?.action_type;
|
|
864
|
+
if (!wire || !actionType) {
|
|
865
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
866
|
+
res.end(
|
|
867
|
+
JSON.stringify({ error: "Missing action_context.action_type in request body" })
|
|
868
|
+
);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
const handler = handlerMap.get(actionType);
|
|
872
|
+
if (!handler) {
|
|
873
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
874
|
+
res.end(
|
|
875
|
+
JSON.stringify({
|
|
876
|
+
error: `No handler registered for action type: ${actionType}`
|
|
877
|
+
})
|
|
878
|
+
);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const requestIdHeader = req.headers["x-planes-request-id"];
|
|
882
|
+
const requestId = Array.isArray(requestIdHeader) ? requestIdHeader[0] : requestIdHeader;
|
|
883
|
+
const context = {
|
|
884
|
+
action_id: wire.action_id,
|
|
885
|
+
action_type: actionType,
|
|
886
|
+
entity_type: wire.entity_type,
|
|
887
|
+
entity_id: wire.entity_id,
|
|
888
|
+
tenant_id: wire.tenant_id,
|
|
889
|
+
data: wire.data ?? {},
|
|
890
|
+
stage: wire.stage ?? null,
|
|
891
|
+
metadata: wire.metadata,
|
|
892
|
+
callback_token: envelope.callback_token,
|
|
893
|
+
callback_base_url: envelope.callback_base_url,
|
|
894
|
+
// Only set request_id when the header was present (exactOptionalPropertyTypes).
|
|
895
|
+
...requestId !== void 0 ? { request_id: requestId } : {}
|
|
896
|
+
};
|
|
897
|
+
let result;
|
|
898
|
+
try {
|
|
899
|
+
result = await handler.execute(context);
|
|
900
|
+
} catch (err) {
|
|
901
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown handler error";
|
|
902
|
+
if (onError && err instanceof Error) {
|
|
903
|
+
onError(err);
|
|
904
|
+
}
|
|
905
|
+
result = {
|
|
906
|
+
status: "failed",
|
|
907
|
+
error: errorMessage,
|
|
908
|
+
retryable: false
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
912
|
+
res.end(JSON.stringify(result));
|
|
913
|
+
} catch (err) {
|
|
914
|
+
if (onError && err instanceof Error) {
|
|
915
|
+
onError(err);
|
|
916
|
+
}
|
|
917
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
918
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
server.listen(port);
|
|
922
|
+
const shutdown = () => {
|
|
923
|
+
server.close();
|
|
924
|
+
};
|
|
925
|
+
process.on("SIGTERM", shutdown);
|
|
926
|
+
process.on("SIGINT", shutdown);
|
|
927
|
+
return server;
|
|
928
|
+
}
|
|
929
|
+
var Planes = {
|
|
430
930
|
/**
|
|
431
931
|
* Register action handlers and start an HTTP server to receive action requests.
|
|
432
932
|
*
|
|
433
933
|
* @example
|
|
434
934
|
* ```ts
|
|
435
|
-
*
|
|
935
|
+
* Planes.register([
|
|
436
936
|
* {
|
|
437
937
|
* type: 'send_email',
|
|
438
938
|
* async execute(ctx) {
|
|
@@ -444,77 +944,45 @@ var Blueprint = {
|
|
|
444
944
|
* ```
|
|
445
945
|
*/
|
|
446
946
|
register(handlers, options) {
|
|
447
|
-
const port = options?.port ?? 3e3;
|
|
448
|
-
const basePath = options?.path ?? "/actions";
|
|
449
|
-
const onError = options?.onError;
|
|
450
947
|
const handlerMap = /* @__PURE__ */ new Map();
|
|
451
948
|
for (const handler of handlers) {
|
|
452
949
|
handlerMap.set(handler.type, handler);
|
|
453
950
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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;
|
|
951
|
+
return createActionServer(handlerMap, options);
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
var AgentApp = class {
|
|
955
|
+
constructor() {
|
|
956
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Register a handler for an action type. Accepts a bare `execute` function
|
|
960
|
+
* (the caller does not build the `{ type, execute }` object).
|
|
961
|
+
*
|
|
962
|
+
* @param type - The action type this handler responds to.
|
|
963
|
+
* @param execute - The handler function invoked with the {@link ActionContext}.
|
|
964
|
+
* @returns `this`, for chaining.
|
|
965
|
+
*/
|
|
966
|
+
registerAction(type, execute) {
|
|
967
|
+
this.handlers.set(type, { type, execute });
|
|
968
|
+
return this;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Start an HTTP server that dispatches incoming action requests to the
|
|
972
|
+
* registered handlers. Delegates to the same server factory as
|
|
973
|
+
* {@link Planes.register}.
|
|
974
|
+
*
|
|
975
|
+
* The handler set is snapshotted at call time, so registering further actions
|
|
976
|
+
* after `listen()` does not mutate the running server (parity with
|
|
977
|
+
* `Planes.register`, which snapshots its array).
|
|
978
|
+
*/
|
|
979
|
+
listen(options) {
|
|
980
|
+
return createActionServer(new Map(this.handlers), options);
|
|
516
981
|
}
|
|
517
982
|
};
|
|
983
|
+
function createAgentApp() {
|
|
984
|
+
return new AgentApp();
|
|
985
|
+
}
|
|
518
986
|
function readBody(req) {
|
|
519
987
|
return new Promise((resolve, reject) => {
|
|
520
988
|
const chunks = [];
|
|
@@ -523,121 +991,43 @@ function readBody(req) {
|
|
|
523
991
|
req.on("error", reject);
|
|
524
992
|
});
|
|
525
993
|
}
|
|
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
994
|
// Annotate the CommonJS export names for ESM import in node:
|
|
615
995
|
0 && (module.exports = {
|
|
996
|
+
ActivityResource,
|
|
997
|
+
AgentApp,
|
|
616
998
|
AuthenticationError,
|
|
617
999
|
AuthorizationError,
|
|
618
1000
|
BaseResource,
|
|
619
|
-
Blueprint,
|
|
620
|
-
BlueprintClient,
|
|
621
|
-
BlueprintError,
|
|
622
1001
|
CalendarEventResource,
|
|
1002
|
+
ContactChannelResource,
|
|
1003
|
+
ContactResource,
|
|
623
1004
|
ContractResource,
|
|
624
1005
|
CustomerResource,
|
|
625
1006
|
DEFAULT_BASE_URL,
|
|
626
1007
|
DEFAULT_MAX_RETRIES,
|
|
627
1008
|
DEFAULT_TIMEOUT,
|
|
1009
|
+
DEFAULT_TOKEN_REFRESH_SKEW_MS,
|
|
628
1010
|
EstimateResource,
|
|
629
1011
|
InventoryResource,
|
|
630
1012
|
InvoiceResource,
|
|
631
1013
|
JobResource,
|
|
1014
|
+
MemoryTokenStorage,
|
|
632
1015
|
NotFoundError,
|
|
633
1016
|
OAuthError,
|
|
634
1017
|
OpportunityResource,
|
|
1018
|
+
Planes,
|
|
1019
|
+
PlanesClient,
|
|
1020
|
+
PlanesError,
|
|
635
1021
|
PropertyResource,
|
|
636
1022
|
RateLimitError,
|
|
637
1023
|
ServerError,
|
|
1024
|
+
TagResource,
|
|
638
1025
|
ValidationError,
|
|
1026
|
+
WebhookRouter,
|
|
639
1027
|
WebhookVerificationError,
|
|
640
1028
|
buildAuthorizeUrl,
|
|
1029
|
+
clientCredentialsGrant,
|
|
1030
|
+
createAgentApp,
|
|
641
1031
|
exchangeCode,
|
|
642
1032
|
parseWebhookEvent,
|
|
643
1033
|
refreshAccessToken,
|