@timesheet/plugin-freshbooks 1.0.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 +59 -0
- package/dist/handlers/handleSyncBatch.d.ts +4 -0
- package/dist/handlers/handleSyncBatch.js +82 -0
- package/dist/handlers/handleWebhook.d.ts +3 -0
- package/dist/handlers/handleWebhook.js +12 -0
- package/dist/handlers/listExternalProjects.d.ts +3 -0
- package/dist/handlers/listExternalProjects.js +14 -0
- package/dist/handlers/listExternalServices.d.ts +3 -0
- package/dist/handlers/listExternalServices.js +14 -0
- package/dist/handlers/listExternalUsers.d.ts +3 -0
- package/dist/handlers/listExternalUsers.js +14 -0
- package/dist/handlers/registerWebhooks.d.ts +3 -0
- package/dist/handlers/registerWebhooks.js +11 -0
- package/dist/handlers/runFullSync.d.ts +3 -0
- package/dist/handlers/runFullSync.js +12 -0
- package/dist/handlers/syncTaskFromExternal.d.ts +3 -0
- package/dist/handlers/syncTaskFromExternal.js +12 -0
- package/dist/handlers/syncTaskToExternal.d.ts +3 -0
- package/dist/handlers/syncTaskToExternal.js +13 -0
- package/dist/handlers/testConnection.d.ts +7 -0
- package/dist/handlers/testConnection.js +27 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +25 -0
- package/dist/lib/freshbooksClient.d.ts +47 -0
- package/dist/lib/freshbooksClient.js +289 -0
- package/dist/lib/taskSync.d.ts +22 -0
- package/dist/lib/taskSync.js +739 -0
- package/dist/lib/types.d.ts +153 -0
- package/dist/lib/types.js +2 -0
- package/manifest.json +275 -0
- package/package.json +22 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { TaskDto } from '@timesheet/integration-sdk';
|
|
2
|
+
export interface FreshBooksConfig {
|
|
3
|
+
syncDirection?: 'bidirectional' | 'timesheet-to-freshbooks' | 'freshbooks-to-timesheet' | 'timesheet-to-external' | 'external-to-timesheet';
|
|
4
|
+
/**
|
|
5
|
+
* Optional override selecting which FreshBooks business to sync when the
|
|
6
|
+
* connected identity belongs to more than one. Matches `business.id`.
|
|
7
|
+
*/
|
|
8
|
+
businessId?: string;
|
|
9
|
+
/** Fallback webhook verifier; normally captured during the callback handshake. */
|
|
10
|
+
webhookSecret?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface FreshBooksTimer {
|
|
13
|
+
id?: number;
|
|
14
|
+
is_running?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface FreshBooksTimeEntry {
|
|
17
|
+
id: number;
|
|
18
|
+
is_logged?: boolean;
|
|
19
|
+
duration?: number;
|
|
20
|
+
note?: string;
|
|
21
|
+
started_at?: string;
|
|
22
|
+
created_at?: string;
|
|
23
|
+
client_id?: number;
|
|
24
|
+
project_id?: number;
|
|
25
|
+
service_id?: number;
|
|
26
|
+
identity_id?: number;
|
|
27
|
+
active?: boolean;
|
|
28
|
+
billable?: boolean;
|
|
29
|
+
billed?: boolean;
|
|
30
|
+
internal?: boolean;
|
|
31
|
+
retainer_id?: number;
|
|
32
|
+
timer?: FreshBooksTimer;
|
|
33
|
+
}
|
|
34
|
+
export interface FreshBooksProject {
|
|
35
|
+
id: number;
|
|
36
|
+
title?: string;
|
|
37
|
+
client_id?: number;
|
|
38
|
+
active?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface FreshBooksService {
|
|
41
|
+
id: number;
|
|
42
|
+
business_id?: number;
|
|
43
|
+
name?: string;
|
|
44
|
+
billable?: boolean;
|
|
45
|
+
vis_state?: number;
|
|
46
|
+
}
|
|
47
|
+
export interface FreshBooksTeamMember {
|
|
48
|
+
identity_id?: number;
|
|
49
|
+
uuid?: string;
|
|
50
|
+
first_name?: string;
|
|
51
|
+
last_name?: string;
|
|
52
|
+
email?: string;
|
|
53
|
+
active?: boolean;
|
|
54
|
+
business_role_name?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface FreshBooksBusiness {
|
|
57
|
+
id: number;
|
|
58
|
+
account_id: string;
|
|
59
|
+
name?: string;
|
|
60
|
+
active?: boolean;
|
|
61
|
+
}
|
|
62
|
+
export interface FreshBooksBusinessMembership {
|
|
63
|
+
id?: number;
|
|
64
|
+
role?: string;
|
|
65
|
+
business?: FreshBooksBusiness;
|
|
66
|
+
}
|
|
67
|
+
export interface FreshBooksMeResponse {
|
|
68
|
+
response?: {
|
|
69
|
+
id?: number;
|
|
70
|
+
business_memberships?: FreshBooksBusinessMembership[];
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export interface FreshBooksMeta {
|
|
74
|
+
page?: number;
|
|
75
|
+
pages?: number;
|
|
76
|
+
per_page?: number;
|
|
77
|
+
total?: number;
|
|
78
|
+
}
|
|
79
|
+
export interface FreshBooksTimeEntriesResponse {
|
|
80
|
+
time_entries?: FreshBooksTimeEntry[];
|
|
81
|
+
meta?: FreshBooksMeta;
|
|
82
|
+
}
|
|
83
|
+
export interface FreshBooksTimeEntryResponse {
|
|
84
|
+
time_entry?: FreshBooksTimeEntry;
|
|
85
|
+
}
|
|
86
|
+
export interface FreshBooksProjectsResponse {
|
|
87
|
+
projects?: FreshBooksProject[];
|
|
88
|
+
meta?: FreshBooksMeta;
|
|
89
|
+
}
|
|
90
|
+
export interface FreshBooksProjectResponse {
|
|
91
|
+
project?: FreshBooksProject;
|
|
92
|
+
}
|
|
93
|
+
export interface FreshBooksServicesResponse {
|
|
94
|
+
services?: FreshBooksService[];
|
|
95
|
+
meta?: FreshBooksMeta;
|
|
96
|
+
}
|
|
97
|
+
export interface FreshBooksTeamMembersResponse {
|
|
98
|
+
response?: FreshBooksTeamMember[];
|
|
99
|
+
meta?: FreshBooksMeta;
|
|
100
|
+
}
|
|
101
|
+
export interface FreshBooksCallback {
|
|
102
|
+
callbackid?: number;
|
|
103
|
+
event?: string;
|
|
104
|
+
uri?: string;
|
|
105
|
+
verified?: boolean;
|
|
106
|
+
verifier?: string;
|
|
107
|
+
}
|
|
108
|
+
export interface FreshBooksCallbackResponse {
|
|
109
|
+
response?: {
|
|
110
|
+
result?: {
|
|
111
|
+
callback?: FreshBooksCallback;
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export interface FreshBooksCallbacksResponse {
|
|
116
|
+
response?: {
|
|
117
|
+
result?: {
|
|
118
|
+
callbacks?: FreshBooksCallback[];
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export interface FreshBooksWebhookPayload {
|
|
123
|
+
name?: string;
|
|
124
|
+
object_id?: string | number;
|
|
125
|
+
account_id?: string;
|
|
126
|
+
business_id?: string | number;
|
|
127
|
+
identity_id?: string | number;
|
|
128
|
+
verifier?: string;
|
|
129
|
+
verified?: string | boolean;
|
|
130
|
+
system?: string;
|
|
131
|
+
user_id?: string | number;
|
|
132
|
+
}
|
|
133
|
+
export interface SyncInput {
|
|
134
|
+
event?: string;
|
|
135
|
+
triggerId?: string;
|
|
136
|
+
taskId?: string;
|
|
137
|
+
entityId?: string;
|
|
138
|
+
item?: Partial<TaskDto> & {
|
|
139
|
+
taskId?: string;
|
|
140
|
+
id?: string;
|
|
141
|
+
projectId?: string;
|
|
142
|
+
userId?: string;
|
|
143
|
+
rateId?: string;
|
|
144
|
+
};
|
|
145
|
+
externalTaskId?: string;
|
|
146
|
+
method?: string;
|
|
147
|
+
headers?: Record<string, string>;
|
|
148
|
+
query?: Record<string, string>;
|
|
149
|
+
body?: unknown;
|
|
150
|
+
rawBody?: string;
|
|
151
|
+
/** Set by the backend on webhooks it has already verified and routed. */
|
|
152
|
+
verified?: boolean;
|
|
153
|
+
}
|
package/manifest.json
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "freshbooks-sync",
|
|
3
|
+
"name": "FreshBooks Sync",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Synchronize time entries with FreshBooks",
|
|
6
|
+
"longDescription": "Bidirectional synchronization between Timesheet tasks and FreshBooks time entries. Timesheet projects map to FreshBooks projects and Timesheet users map to FreshBooks team members, so logged time lands on the right project and teammate in FreshBooks for billing.",
|
|
7
|
+
"icon": "freshbooks",
|
|
8
|
+
"category": "accounting",
|
|
9
|
+
"tags": [
|
|
10
|
+
"sync",
|
|
11
|
+
"timesheet",
|
|
12
|
+
"freshbooks",
|
|
13
|
+
"time-tracking"
|
|
14
|
+
],
|
|
15
|
+
"dataAccess": [
|
|
16
|
+
"projects",
|
|
17
|
+
"tasks",
|
|
18
|
+
"colleagues"
|
|
19
|
+
],
|
|
20
|
+
"externalAuth": [
|
|
21
|
+
{
|
|
22
|
+
"id": "freshbooks",
|
|
23
|
+
"type": "oauth2",
|
|
24
|
+
"oauth": {
|
|
25
|
+
"authorizeUrl": "https://auth.freshbooks.com/oauth/authorize",
|
|
26
|
+
"tokenUrl": "https://api.freshbooks.com/auth/oauth/token",
|
|
27
|
+
"scopes": [
|
|
28
|
+
"user:profile:read",
|
|
29
|
+
"user:time_entries:read",
|
|
30
|
+
"user:time_entries:write",
|
|
31
|
+
"user:projects:read",
|
|
32
|
+
"user:clients:read",
|
|
33
|
+
"user:billable_items:read",
|
|
34
|
+
"user:teams:read"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"configSchema": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"syncDirection": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"title": "Sync Direction",
|
|
45
|
+
"enum": [
|
|
46
|
+
"bidirectional",
|
|
47
|
+
"timesheet-to-freshbooks",
|
|
48
|
+
"freshbooks-to-timesheet"
|
|
49
|
+
],
|
|
50
|
+
"default": "bidirectional",
|
|
51
|
+
"x-ui-widget": "radio"
|
|
52
|
+
},
|
|
53
|
+
"businessId": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"title": "FreshBooks Business",
|
|
56
|
+
"x-ui-help": "Optional. When the connected account belongs to more than one FreshBooks business, set the business id to sync against."
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"required": [
|
|
60
|
+
"syncDirection"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"mappingSchema": {
|
|
64
|
+
"system": "freshbooks",
|
|
65
|
+
"mappings": [
|
|
66
|
+
{
|
|
67
|
+
"id": "projects",
|
|
68
|
+
"title": "Project Mapping",
|
|
69
|
+
"description": "Map Timesheet projects to FreshBooks projects",
|
|
70
|
+
"localEntity": {
|
|
71
|
+
"type": "project",
|
|
72
|
+
"label": "Timesheet Project",
|
|
73
|
+
"display": "{{title}}"
|
|
74
|
+
},
|
|
75
|
+
"externalEntity": {
|
|
76
|
+
"type": "project",
|
|
77
|
+
"label": "FreshBooks Project",
|
|
78
|
+
"fetchAction": "list-external-projects"
|
|
79
|
+
},
|
|
80
|
+
"required": true,
|
|
81
|
+
"minMappings": 1
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "users",
|
|
85
|
+
"title": "User Mapping",
|
|
86
|
+
"description": "Map Timesheet users to FreshBooks team members",
|
|
87
|
+
"localEntity": {
|
|
88
|
+
"type": "user",
|
|
89
|
+
"label": "Timesheet User",
|
|
90
|
+
"display": "{{displayName}}"
|
|
91
|
+
},
|
|
92
|
+
"externalEntity": {
|
|
93
|
+
"type": "teammember",
|
|
94
|
+
"label": "FreshBooks Team Member",
|
|
95
|
+
"fetchAction": "list-external-users"
|
|
96
|
+
},
|
|
97
|
+
"required": true,
|
|
98
|
+
"minMappings": 1
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "rates",
|
|
102
|
+
"title": "Rate Mapping",
|
|
103
|
+
"description": "Map Timesheet rates to FreshBooks services",
|
|
104
|
+
"localEntity": {
|
|
105
|
+
"type": "rate",
|
|
106
|
+
"label": "Timesheet Rate",
|
|
107
|
+
"display": "{{title}}"
|
|
108
|
+
},
|
|
109
|
+
"externalEntity": {
|
|
110
|
+
"type": "service",
|
|
111
|
+
"label": "FreshBooks Service",
|
|
112
|
+
"fetchAction": "list-external-services"
|
|
113
|
+
},
|
|
114
|
+
"required": false
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
"triggers": [
|
|
119
|
+
{
|
|
120
|
+
"id": "task-changed",
|
|
121
|
+
"type": "event",
|
|
122
|
+
"mode": "sync",
|
|
123
|
+
"name": "Task Changed",
|
|
124
|
+
"description": "Sync task create/update/delete events to FreshBooks",
|
|
125
|
+
"events": [
|
|
126
|
+
"task.create",
|
|
127
|
+
"task.update",
|
|
128
|
+
"task.delete"
|
|
129
|
+
],
|
|
130
|
+
"configurable": true,
|
|
131
|
+
"actionId": "handle-sync-batch"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"id": "integration-webhook",
|
|
135
|
+
"type": "webhook",
|
|
136
|
+
"name": "FreshBooks Webhook",
|
|
137
|
+
"description": "Receive inbound time entry changes from FreshBooks",
|
|
138
|
+
"configurable": false,
|
|
139
|
+
"actionId": "handle-webhook",
|
|
140
|
+
"webhook": {
|
|
141
|
+
"signature": {
|
|
142
|
+
"header": "x-freshbooks-hmac-sha256",
|
|
143
|
+
"algorithm": "hmac-sha256",
|
|
144
|
+
"secretConfigKey": "webhookSecret"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"id": "full-sync",
|
|
150
|
+
"type": "schedule",
|
|
151
|
+
"mode": "sync",
|
|
152
|
+
"name": "Scheduled Full Sync",
|
|
153
|
+
"description": "Run a complete reconciliation sync",
|
|
154
|
+
"schedule": "0 2 * * *",
|
|
155
|
+
"timezone": "UTC",
|
|
156
|
+
"configurable": true,
|
|
157
|
+
"events": [
|
|
158
|
+
"task.create",
|
|
159
|
+
"task.update",
|
|
160
|
+
"task.delete"
|
|
161
|
+
],
|
|
162
|
+
"actionId": "handle-sync-batch"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"id": "manual-sync",
|
|
166
|
+
"type": "user_action",
|
|
167
|
+
"name": "Manual Sync",
|
|
168
|
+
"description": "Run a full sync from settings",
|
|
169
|
+
"userAction": {
|
|
170
|
+
"label": "Sync Now",
|
|
171
|
+
"placement": "settings"
|
|
172
|
+
},
|
|
173
|
+
"actionId": "run-full-sync"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"id": "register-webhooks-action",
|
|
177
|
+
"type": "user_action",
|
|
178
|
+
"name": "Register Webhooks",
|
|
179
|
+
"description": "Register the FreshBooks callbacks for real-time inbound sync",
|
|
180
|
+
"userAction": {
|
|
181
|
+
"label": "Register Webhooks",
|
|
182
|
+
"placement": "settings"
|
|
183
|
+
},
|
|
184
|
+
"actionId": "register-webhooks"
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
"actions": [
|
|
188
|
+
{
|
|
189
|
+
"id": "sync-task-to-external",
|
|
190
|
+
"name": "Sync task to FreshBooks",
|
|
191
|
+
"handler": "syncTaskToExternal"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"id": "handle-sync-batch",
|
|
195
|
+
"name": "Handle sync batch",
|
|
196
|
+
"handler": "handleSyncBatch"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"id": "sync-task-from-external",
|
|
200
|
+
"name": "Sync task from FreshBooks",
|
|
201
|
+
"handler": "syncTaskFromExternal",
|
|
202
|
+
"internal": true
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"id": "handle-webhook",
|
|
206
|
+
"handler": "handleWebhook",
|
|
207
|
+
"internal": true
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"id": "run-full-sync",
|
|
211
|
+
"name": "Run full sync",
|
|
212
|
+
"handler": "runFullSync"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"id": "register-webhooks",
|
|
216
|
+
"name": "Register webhooks",
|
|
217
|
+
"handler": "registerWebhooks"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"id": "test-connection",
|
|
221
|
+
"name": "Test connection",
|
|
222
|
+
"handler": "testConnection"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"id": "list-external-projects",
|
|
226
|
+
"handler": "listExternalProjects",
|
|
227
|
+
"internal": true
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"id": "list-external-users",
|
|
231
|
+
"handler": "listExternalUsers",
|
|
232
|
+
"internal": true
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"id": "list-external-services",
|
|
236
|
+
"handler": "listExternalServices",
|
|
237
|
+
"internal": true
|
|
238
|
+
}
|
|
239
|
+
],
|
|
240
|
+
"pages": [
|
|
241
|
+
{
|
|
242
|
+
"id": "dashboard",
|
|
243
|
+
"title": "Sync Dashboard",
|
|
244
|
+
"icon": "chart-bar",
|
|
245
|
+
"layout": "dashboard",
|
|
246
|
+
"widgets": [
|
|
247
|
+
{
|
|
248
|
+
"id": "last-sync",
|
|
249
|
+
"type": "metric",
|
|
250
|
+
"title": "Last Sync"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"id": "sync-stats",
|
|
254
|
+
"type": "metric",
|
|
255
|
+
"title": "Sync Statistics"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"id": "connection-health",
|
|
259
|
+
"type": "metric",
|
|
260
|
+
"title": "Connection Health"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"id": "manual-sync-btn",
|
|
264
|
+
"type": "action_button",
|
|
265
|
+
"title": "Manual Sync",
|
|
266
|
+
"config": {
|
|
267
|
+
"actionId": "run-full-sync",
|
|
268
|
+
"label": "Run Full Sync",
|
|
269
|
+
"variant": "primary"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
]
|
|
275
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@timesheet/plugin-freshbooks",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"manifest.json"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@timesheet/integration-sdk": "^0.5.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^20.19.43",
|
|
20
|
+
"typescript": "^5.9.3"
|
|
21
|
+
}
|
|
22
|
+
}
|