@loculabs/api-client 1.3.0 → 1.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/CHANGELOG.md +12 -0
- package/dist/index.d.mts +157 -6
- package/dist/index.d.ts +157 -6
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# [1.4.0](https://github.com/loculabs/api-client/compare/v1.3.0...v1.4.0) (2026-01-28)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **types:** make keepBreaks optional in request types ([7c88e5c](https://github.com/loculabs/api-client/commit/7c88e5cc82954c79e16345e867304610e09ddf22))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **sessions:** add activities list endpoint ([3a4f267](https://github.com/loculabs/api-client/commit/3a4f26772d37610ba55d2ce8834bcd635dc9ba76))
|
|
12
|
+
|
|
1
13
|
# [1.3.0](https://github.com/loculabs/api-client/compare/v1.2.0...v1.3.0) (2026-01-27)
|
|
2
14
|
|
|
3
15
|
|
package/dist/index.d.mts
CHANGED
|
@@ -512,6 +512,10 @@ interface paths {
|
|
|
512
512
|
limit?: number;
|
|
513
513
|
/** @description Cursor for pagination */
|
|
514
514
|
cursor?: string;
|
|
515
|
+
/** @description Field to order results by */
|
|
516
|
+
orderBy?: "updatedAt" | "createdAt";
|
|
517
|
+
/** @description Sort order (ascending or descending) */
|
|
518
|
+
order?: "asc" | "desc";
|
|
515
519
|
/** @description Filter by project state */
|
|
516
520
|
state?: "planned" | "completed";
|
|
517
521
|
/** @description Include HTML representation of the project description */
|
|
@@ -919,6 +923,10 @@ interface paths {
|
|
|
919
923
|
limit?: number;
|
|
920
924
|
/** @description Cursor for pagination */
|
|
921
925
|
cursor?: string;
|
|
926
|
+
/** @description Field to order results by */
|
|
927
|
+
orderBy?: "updatedAt" | "createdAt" | "finishedAt";
|
|
928
|
+
/** @description Sort order (ascending or descending) */
|
|
929
|
+
order?: "asc" | "desc";
|
|
922
930
|
/** @description Filter sessions starting after this date (Unix seconds or ISO 8601) */
|
|
923
931
|
startAfter?: string;
|
|
924
932
|
/** @description Filter sessions starting before this date (Unix seconds or ISO 8601) */
|
|
@@ -1068,6 +1076,103 @@ interface paths {
|
|
|
1068
1076
|
patch?: never;
|
|
1069
1077
|
trace?: never;
|
|
1070
1078
|
};
|
|
1079
|
+
"/sessions/activities": {
|
|
1080
|
+
parameters: {
|
|
1081
|
+
query?: never;
|
|
1082
|
+
header?: never;
|
|
1083
|
+
path?: never;
|
|
1084
|
+
cookie?: never;
|
|
1085
|
+
};
|
|
1086
|
+
/**
|
|
1087
|
+
* List activities
|
|
1088
|
+
* @description Retrieve a paginated list of session activities. Supports filtering by task ID or session ID.
|
|
1089
|
+
*/
|
|
1090
|
+
get: {
|
|
1091
|
+
parameters: {
|
|
1092
|
+
query?: {
|
|
1093
|
+
/** @description Number of activities to return */
|
|
1094
|
+
limit?: number;
|
|
1095
|
+
/** @description Cursor for pagination */
|
|
1096
|
+
cursor?: string;
|
|
1097
|
+
/** @description Field to order results by */
|
|
1098
|
+
orderBy?: "updatedAt" | "createdAt";
|
|
1099
|
+
/** @description Sort order (ascending or descending) */
|
|
1100
|
+
order?: "asc" | "desc";
|
|
1101
|
+
/** @description Filter activities by task ID */
|
|
1102
|
+
taskId?: string;
|
|
1103
|
+
/** @description Filter activities by session ID */
|
|
1104
|
+
sessionId?: string;
|
|
1105
|
+
};
|
|
1106
|
+
header?: never;
|
|
1107
|
+
path?: never;
|
|
1108
|
+
cookie?: never;
|
|
1109
|
+
};
|
|
1110
|
+
requestBody?: never;
|
|
1111
|
+
responses: {
|
|
1112
|
+
/** @description Successful response */
|
|
1113
|
+
200: {
|
|
1114
|
+
headers: {
|
|
1115
|
+
[name: string]: unknown;
|
|
1116
|
+
};
|
|
1117
|
+
content: {
|
|
1118
|
+
"application/json": components["schemas"]["ActivityPaginatedListResponse"];
|
|
1119
|
+
};
|
|
1120
|
+
};
|
|
1121
|
+
/** @description Bad request - validation error */
|
|
1122
|
+
400: {
|
|
1123
|
+
headers: {
|
|
1124
|
+
[name: string]: unknown;
|
|
1125
|
+
};
|
|
1126
|
+
content: {
|
|
1127
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
1128
|
+
};
|
|
1129
|
+
};
|
|
1130
|
+
/** @description Unauthorized - invalid or missing API key */
|
|
1131
|
+
401: {
|
|
1132
|
+
headers: {
|
|
1133
|
+
[name: string]: unknown;
|
|
1134
|
+
};
|
|
1135
|
+
content: {
|
|
1136
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
1137
|
+
};
|
|
1138
|
+
};
|
|
1139
|
+
/** @description Resource not found */
|
|
1140
|
+
404: {
|
|
1141
|
+
headers: {
|
|
1142
|
+
[name: string]: unknown;
|
|
1143
|
+
};
|
|
1144
|
+
content: {
|
|
1145
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
1146
|
+
};
|
|
1147
|
+
};
|
|
1148
|
+
/** @description Rate limit exceeded */
|
|
1149
|
+
429: {
|
|
1150
|
+
headers: {
|
|
1151
|
+
[name: string]: unknown;
|
|
1152
|
+
};
|
|
1153
|
+
content: {
|
|
1154
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
1155
|
+
};
|
|
1156
|
+
};
|
|
1157
|
+
/** @description Internal server error */
|
|
1158
|
+
500: {
|
|
1159
|
+
headers: {
|
|
1160
|
+
[name: string]: unknown;
|
|
1161
|
+
};
|
|
1162
|
+
content: {
|
|
1163
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
1164
|
+
};
|
|
1165
|
+
};
|
|
1166
|
+
};
|
|
1167
|
+
};
|
|
1168
|
+
put?: never;
|
|
1169
|
+
post?: never;
|
|
1170
|
+
delete?: never;
|
|
1171
|
+
options?: never;
|
|
1172
|
+
head?: never;
|
|
1173
|
+
patch?: never;
|
|
1174
|
+
trace?: never;
|
|
1175
|
+
};
|
|
1071
1176
|
"/sessions/{id}": {
|
|
1072
1177
|
parameters: {
|
|
1073
1178
|
query?: never;
|
|
@@ -1647,6 +1752,10 @@ interface paths {
|
|
|
1647
1752
|
cursor?: string;
|
|
1648
1753
|
/** @description Number of items to return (max 100) */
|
|
1649
1754
|
limit?: string;
|
|
1755
|
+
/** @description Field to order results by. Note: doneAt ordering only applies to completed tasks */
|
|
1756
|
+
orderBy?: "updatedAt" | "doneAt";
|
|
1757
|
+
/** @description Sort order (ascending or descending) */
|
|
1758
|
+
order?: "asc" | "desc";
|
|
1650
1759
|
/** @description Filter by completion status */
|
|
1651
1760
|
done?: "true" | "false";
|
|
1652
1761
|
/** @description Filter by project */
|
|
@@ -3710,6 +3819,11 @@ interface components {
|
|
|
3710
3819
|
color?: string | null;
|
|
3711
3820
|
/** @description Parent folder ID */
|
|
3712
3821
|
folderId?: string;
|
|
3822
|
+
/**
|
|
3823
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
3824
|
+
* @default true
|
|
3825
|
+
*/
|
|
3826
|
+
keepBreaks: boolean;
|
|
3713
3827
|
};
|
|
3714
3828
|
UpdateNoteRequest: {
|
|
3715
3829
|
/** @description New text content for the note */
|
|
@@ -3720,6 +3834,11 @@ interface components {
|
|
|
3720
3834
|
color?: string | null;
|
|
3721
3835
|
/** @description New parent folder ID */
|
|
3722
3836
|
folderId?: string | null;
|
|
3837
|
+
/**
|
|
3838
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
3839
|
+
* @default true
|
|
3840
|
+
*/
|
|
3841
|
+
keepBreaks: boolean;
|
|
3723
3842
|
};
|
|
3724
3843
|
DeleteNoteResponse: {
|
|
3725
3844
|
success: boolean;
|
|
@@ -3817,6 +3936,11 @@ interface components {
|
|
|
3817
3936
|
icon?: string | null;
|
|
3818
3937
|
/** @description Hex color for the icon (e.g., "#FF5733"). Only applies to Lucide icons, not emojis. */
|
|
3819
3938
|
color?: string | null;
|
|
3939
|
+
/**
|
|
3940
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
3941
|
+
* @default true
|
|
3942
|
+
*/
|
|
3943
|
+
keepBreaks: boolean;
|
|
3820
3944
|
};
|
|
3821
3945
|
UpdateProjectRequest: {
|
|
3822
3946
|
/** @description New name for the project */
|
|
@@ -3832,6 +3956,11 @@ interface components {
|
|
|
3832
3956
|
* @enum {string}
|
|
3833
3957
|
*/
|
|
3834
3958
|
state?: "planned" | "completed";
|
|
3959
|
+
/**
|
|
3960
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
3961
|
+
* @default true
|
|
3962
|
+
*/
|
|
3963
|
+
keepBreaks: boolean;
|
|
3835
3964
|
};
|
|
3836
3965
|
DeleteProjectResponse: {
|
|
3837
3966
|
success: boolean;
|
|
@@ -3896,6 +4025,11 @@ interface components {
|
|
|
3896
4025
|
/** @description End timestamp (Unix seconds) */
|
|
3897
4026
|
finishedAt: number;
|
|
3898
4027
|
};
|
|
4028
|
+
ActivityPaginatedListResponse: {
|
|
4029
|
+
data: components["schemas"]["SessionActivity"][];
|
|
4030
|
+
nextCursor: string | null;
|
|
4031
|
+
hasMore: boolean;
|
|
4032
|
+
};
|
|
3899
4033
|
CreateSessionRequest: {
|
|
3900
4034
|
/**
|
|
3901
4035
|
* Format: uuid
|
|
@@ -3988,6 +4122,11 @@ interface components {
|
|
|
3988
4122
|
* @enum {string}
|
|
3989
4123
|
*/
|
|
3990
4124
|
section?: "today" | "sooner" | "later";
|
|
4125
|
+
/**
|
|
4126
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
4127
|
+
* @default true
|
|
4128
|
+
*/
|
|
4129
|
+
keepBreaks: boolean;
|
|
3991
4130
|
};
|
|
3992
4131
|
UpdateTaskRequest: {
|
|
3993
4132
|
/** @description Task name */
|
|
@@ -4005,6 +4144,11 @@ interface components {
|
|
|
4005
4144
|
waiting?: {
|
|
4006
4145
|
reason?: string;
|
|
4007
4146
|
} | null;
|
|
4147
|
+
/**
|
|
4148
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
4149
|
+
* @default true
|
|
4150
|
+
*/
|
|
4151
|
+
keepBreaks: boolean;
|
|
4008
4152
|
};
|
|
4009
4153
|
DeleteTaskResponse: {
|
|
4010
4154
|
success: boolean;
|
|
@@ -4173,16 +4317,20 @@ interface components {
|
|
|
4173
4317
|
type $defs = Record<string, never>;
|
|
4174
4318
|
type operations = Record<string, never>;
|
|
4175
4319
|
|
|
4320
|
+
/** Helper to make keepBreaks optional (has server default) */
|
|
4321
|
+
type WithOptionalKeepBreaks<T> = Omit<T, "keepBreaks"> & {
|
|
4322
|
+
keepBreaks?: boolean;
|
|
4323
|
+
};
|
|
4176
4324
|
type Task = components["schemas"]["Task"];
|
|
4177
|
-
type CreateTaskRequest = components["schemas"]["CreateTaskRequest"]
|
|
4178
|
-
type UpdateTaskRequest = components["schemas"]["UpdateTaskRequest"]
|
|
4325
|
+
type CreateTaskRequest = WithOptionalKeepBreaks<components["schemas"]["CreateTaskRequest"]>;
|
|
4326
|
+
type UpdateTaskRequest = WithOptionalKeepBreaks<components["schemas"]["UpdateTaskRequest"]>;
|
|
4179
4327
|
type TaskSectionsResponse = components["schemas"]["TaskSectionsResponse"];
|
|
4180
4328
|
type Project = components["schemas"]["Project"];
|
|
4181
|
-
type CreateProjectRequest = components["schemas"]["CreateProjectRequest"]
|
|
4182
|
-
type UpdateProjectRequest = components["schemas"]["UpdateProjectRequest"]
|
|
4329
|
+
type CreateProjectRequest = WithOptionalKeepBreaks<components["schemas"]["CreateProjectRequest"]>;
|
|
4330
|
+
type UpdateProjectRequest = WithOptionalKeepBreaks<components["schemas"]["UpdateProjectRequest"]>;
|
|
4183
4331
|
type Note = components["schemas"]["Note"];
|
|
4184
|
-
type CreateNoteRequest = components["schemas"]["CreateNoteRequest"]
|
|
4185
|
-
type UpdateNoteRequest = components["schemas"]["UpdateNoteRequest"]
|
|
4332
|
+
type CreateNoteRequest = WithOptionalKeepBreaks<components["schemas"]["CreateNoteRequest"]>;
|
|
4333
|
+
type UpdateNoteRequest = WithOptionalKeepBreaks<components["schemas"]["UpdateNoteRequest"]>;
|
|
4186
4334
|
type Session = components["schemas"]["Session"];
|
|
4187
4335
|
type SessionWithActivities = components["schemas"]["SessionWithActivities"];
|
|
4188
4336
|
type SessionActivity = components["schemas"]["SessionActivity"];
|
|
@@ -4230,6 +4378,7 @@ type SubtaskListParams = Omit<NonNullable<paths["/tasks/{id}/subtasks"]["get"]["
|
|
|
4230
4378
|
type ProjectListParams = NonNullable<paths["/projects"]["get"]["parameters"]["query"]>;
|
|
4231
4379
|
type NoteListParams = NonNullable<paths["/notes"]["get"]["parameters"]["query"]>;
|
|
4232
4380
|
type SessionListParams = NonNullable<paths["/sessions"]["get"]["parameters"]["query"]>;
|
|
4381
|
+
type ActivityListParams = NonNullable<paths["/sessions/activities"]["get"]["parameters"]["query"]>;
|
|
4233
4382
|
type WebhookListParams = Omit<NonNullable<paths["/webhooks"]["get"]["parameters"]["query"]>, "isActive"> & {
|
|
4234
4383
|
isActive?: BooleanParam;
|
|
4235
4384
|
};
|
|
@@ -4327,6 +4476,8 @@ declare const createLocuClient: (config: LocuClientConfig) => {
|
|
|
4327
4476
|
delete: (id: string) => Promise<{
|
|
4328
4477
|
success: boolean;
|
|
4329
4478
|
}>;
|
|
4479
|
+
/** List all activities with optional filters */
|
|
4480
|
+
listActivities: (params?: ActivityListParams) => Promise<PaginatedResponse<SessionActivity>>;
|
|
4330
4481
|
activities: {
|
|
4331
4482
|
/** List activities for a session */
|
|
4332
4483
|
list: (sessionId: string) => Promise<{
|
package/dist/index.d.ts
CHANGED
|
@@ -512,6 +512,10 @@ interface paths {
|
|
|
512
512
|
limit?: number;
|
|
513
513
|
/** @description Cursor for pagination */
|
|
514
514
|
cursor?: string;
|
|
515
|
+
/** @description Field to order results by */
|
|
516
|
+
orderBy?: "updatedAt" | "createdAt";
|
|
517
|
+
/** @description Sort order (ascending or descending) */
|
|
518
|
+
order?: "asc" | "desc";
|
|
515
519
|
/** @description Filter by project state */
|
|
516
520
|
state?: "planned" | "completed";
|
|
517
521
|
/** @description Include HTML representation of the project description */
|
|
@@ -919,6 +923,10 @@ interface paths {
|
|
|
919
923
|
limit?: number;
|
|
920
924
|
/** @description Cursor for pagination */
|
|
921
925
|
cursor?: string;
|
|
926
|
+
/** @description Field to order results by */
|
|
927
|
+
orderBy?: "updatedAt" | "createdAt" | "finishedAt";
|
|
928
|
+
/** @description Sort order (ascending or descending) */
|
|
929
|
+
order?: "asc" | "desc";
|
|
922
930
|
/** @description Filter sessions starting after this date (Unix seconds or ISO 8601) */
|
|
923
931
|
startAfter?: string;
|
|
924
932
|
/** @description Filter sessions starting before this date (Unix seconds or ISO 8601) */
|
|
@@ -1068,6 +1076,103 @@ interface paths {
|
|
|
1068
1076
|
patch?: never;
|
|
1069
1077
|
trace?: never;
|
|
1070
1078
|
};
|
|
1079
|
+
"/sessions/activities": {
|
|
1080
|
+
parameters: {
|
|
1081
|
+
query?: never;
|
|
1082
|
+
header?: never;
|
|
1083
|
+
path?: never;
|
|
1084
|
+
cookie?: never;
|
|
1085
|
+
};
|
|
1086
|
+
/**
|
|
1087
|
+
* List activities
|
|
1088
|
+
* @description Retrieve a paginated list of session activities. Supports filtering by task ID or session ID.
|
|
1089
|
+
*/
|
|
1090
|
+
get: {
|
|
1091
|
+
parameters: {
|
|
1092
|
+
query?: {
|
|
1093
|
+
/** @description Number of activities to return */
|
|
1094
|
+
limit?: number;
|
|
1095
|
+
/** @description Cursor for pagination */
|
|
1096
|
+
cursor?: string;
|
|
1097
|
+
/** @description Field to order results by */
|
|
1098
|
+
orderBy?: "updatedAt" | "createdAt";
|
|
1099
|
+
/** @description Sort order (ascending or descending) */
|
|
1100
|
+
order?: "asc" | "desc";
|
|
1101
|
+
/** @description Filter activities by task ID */
|
|
1102
|
+
taskId?: string;
|
|
1103
|
+
/** @description Filter activities by session ID */
|
|
1104
|
+
sessionId?: string;
|
|
1105
|
+
};
|
|
1106
|
+
header?: never;
|
|
1107
|
+
path?: never;
|
|
1108
|
+
cookie?: never;
|
|
1109
|
+
};
|
|
1110
|
+
requestBody?: never;
|
|
1111
|
+
responses: {
|
|
1112
|
+
/** @description Successful response */
|
|
1113
|
+
200: {
|
|
1114
|
+
headers: {
|
|
1115
|
+
[name: string]: unknown;
|
|
1116
|
+
};
|
|
1117
|
+
content: {
|
|
1118
|
+
"application/json": components["schemas"]["ActivityPaginatedListResponse"];
|
|
1119
|
+
};
|
|
1120
|
+
};
|
|
1121
|
+
/** @description Bad request - validation error */
|
|
1122
|
+
400: {
|
|
1123
|
+
headers: {
|
|
1124
|
+
[name: string]: unknown;
|
|
1125
|
+
};
|
|
1126
|
+
content: {
|
|
1127
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
1128
|
+
};
|
|
1129
|
+
};
|
|
1130
|
+
/** @description Unauthorized - invalid or missing API key */
|
|
1131
|
+
401: {
|
|
1132
|
+
headers: {
|
|
1133
|
+
[name: string]: unknown;
|
|
1134
|
+
};
|
|
1135
|
+
content: {
|
|
1136
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
1137
|
+
};
|
|
1138
|
+
};
|
|
1139
|
+
/** @description Resource not found */
|
|
1140
|
+
404: {
|
|
1141
|
+
headers: {
|
|
1142
|
+
[name: string]: unknown;
|
|
1143
|
+
};
|
|
1144
|
+
content: {
|
|
1145
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
1146
|
+
};
|
|
1147
|
+
};
|
|
1148
|
+
/** @description Rate limit exceeded */
|
|
1149
|
+
429: {
|
|
1150
|
+
headers: {
|
|
1151
|
+
[name: string]: unknown;
|
|
1152
|
+
};
|
|
1153
|
+
content: {
|
|
1154
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
1155
|
+
};
|
|
1156
|
+
};
|
|
1157
|
+
/** @description Internal server error */
|
|
1158
|
+
500: {
|
|
1159
|
+
headers: {
|
|
1160
|
+
[name: string]: unknown;
|
|
1161
|
+
};
|
|
1162
|
+
content: {
|
|
1163
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
1164
|
+
};
|
|
1165
|
+
};
|
|
1166
|
+
};
|
|
1167
|
+
};
|
|
1168
|
+
put?: never;
|
|
1169
|
+
post?: never;
|
|
1170
|
+
delete?: never;
|
|
1171
|
+
options?: never;
|
|
1172
|
+
head?: never;
|
|
1173
|
+
patch?: never;
|
|
1174
|
+
trace?: never;
|
|
1175
|
+
};
|
|
1071
1176
|
"/sessions/{id}": {
|
|
1072
1177
|
parameters: {
|
|
1073
1178
|
query?: never;
|
|
@@ -1647,6 +1752,10 @@ interface paths {
|
|
|
1647
1752
|
cursor?: string;
|
|
1648
1753
|
/** @description Number of items to return (max 100) */
|
|
1649
1754
|
limit?: string;
|
|
1755
|
+
/** @description Field to order results by. Note: doneAt ordering only applies to completed tasks */
|
|
1756
|
+
orderBy?: "updatedAt" | "doneAt";
|
|
1757
|
+
/** @description Sort order (ascending or descending) */
|
|
1758
|
+
order?: "asc" | "desc";
|
|
1650
1759
|
/** @description Filter by completion status */
|
|
1651
1760
|
done?: "true" | "false";
|
|
1652
1761
|
/** @description Filter by project */
|
|
@@ -3710,6 +3819,11 @@ interface components {
|
|
|
3710
3819
|
color?: string | null;
|
|
3711
3820
|
/** @description Parent folder ID */
|
|
3712
3821
|
folderId?: string;
|
|
3822
|
+
/**
|
|
3823
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
3824
|
+
* @default true
|
|
3825
|
+
*/
|
|
3826
|
+
keepBreaks: boolean;
|
|
3713
3827
|
};
|
|
3714
3828
|
UpdateNoteRequest: {
|
|
3715
3829
|
/** @description New text content for the note */
|
|
@@ -3720,6 +3834,11 @@ interface components {
|
|
|
3720
3834
|
color?: string | null;
|
|
3721
3835
|
/** @description New parent folder ID */
|
|
3722
3836
|
folderId?: string | null;
|
|
3837
|
+
/**
|
|
3838
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
3839
|
+
* @default true
|
|
3840
|
+
*/
|
|
3841
|
+
keepBreaks: boolean;
|
|
3723
3842
|
};
|
|
3724
3843
|
DeleteNoteResponse: {
|
|
3725
3844
|
success: boolean;
|
|
@@ -3817,6 +3936,11 @@ interface components {
|
|
|
3817
3936
|
icon?: string | null;
|
|
3818
3937
|
/** @description Hex color for the icon (e.g., "#FF5733"). Only applies to Lucide icons, not emojis. */
|
|
3819
3938
|
color?: string | null;
|
|
3939
|
+
/**
|
|
3940
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
3941
|
+
* @default true
|
|
3942
|
+
*/
|
|
3943
|
+
keepBreaks: boolean;
|
|
3820
3944
|
};
|
|
3821
3945
|
UpdateProjectRequest: {
|
|
3822
3946
|
/** @description New name for the project */
|
|
@@ -3832,6 +3956,11 @@ interface components {
|
|
|
3832
3956
|
* @enum {string}
|
|
3833
3957
|
*/
|
|
3834
3958
|
state?: "planned" | "completed";
|
|
3959
|
+
/**
|
|
3960
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
3961
|
+
* @default true
|
|
3962
|
+
*/
|
|
3963
|
+
keepBreaks: boolean;
|
|
3835
3964
|
};
|
|
3836
3965
|
DeleteProjectResponse: {
|
|
3837
3966
|
success: boolean;
|
|
@@ -3896,6 +4025,11 @@ interface components {
|
|
|
3896
4025
|
/** @description End timestamp (Unix seconds) */
|
|
3897
4026
|
finishedAt: number;
|
|
3898
4027
|
};
|
|
4028
|
+
ActivityPaginatedListResponse: {
|
|
4029
|
+
data: components["schemas"]["SessionActivity"][];
|
|
4030
|
+
nextCursor: string | null;
|
|
4031
|
+
hasMore: boolean;
|
|
4032
|
+
};
|
|
3899
4033
|
CreateSessionRequest: {
|
|
3900
4034
|
/**
|
|
3901
4035
|
* Format: uuid
|
|
@@ -3988,6 +4122,11 @@ interface components {
|
|
|
3988
4122
|
* @enum {string}
|
|
3989
4123
|
*/
|
|
3990
4124
|
section?: "today" | "sooner" | "later";
|
|
4125
|
+
/**
|
|
4126
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
4127
|
+
* @default true
|
|
4128
|
+
*/
|
|
4129
|
+
keepBreaks: boolean;
|
|
3991
4130
|
};
|
|
3992
4131
|
UpdateTaskRequest: {
|
|
3993
4132
|
/** @description Task name */
|
|
@@ -4005,6 +4144,11 @@ interface components {
|
|
|
4005
4144
|
waiting?: {
|
|
4006
4145
|
reason?: string;
|
|
4007
4146
|
} | null;
|
|
4147
|
+
/**
|
|
4148
|
+
* @description Preserve extra blank lines as empty paragraphs instead of collapsing them
|
|
4149
|
+
* @default true
|
|
4150
|
+
*/
|
|
4151
|
+
keepBreaks: boolean;
|
|
4008
4152
|
};
|
|
4009
4153
|
DeleteTaskResponse: {
|
|
4010
4154
|
success: boolean;
|
|
@@ -4173,16 +4317,20 @@ interface components {
|
|
|
4173
4317
|
type $defs = Record<string, never>;
|
|
4174
4318
|
type operations = Record<string, never>;
|
|
4175
4319
|
|
|
4320
|
+
/** Helper to make keepBreaks optional (has server default) */
|
|
4321
|
+
type WithOptionalKeepBreaks<T> = Omit<T, "keepBreaks"> & {
|
|
4322
|
+
keepBreaks?: boolean;
|
|
4323
|
+
};
|
|
4176
4324
|
type Task = components["schemas"]["Task"];
|
|
4177
|
-
type CreateTaskRequest = components["schemas"]["CreateTaskRequest"]
|
|
4178
|
-
type UpdateTaskRequest = components["schemas"]["UpdateTaskRequest"]
|
|
4325
|
+
type CreateTaskRequest = WithOptionalKeepBreaks<components["schemas"]["CreateTaskRequest"]>;
|
|
4326
|
+
type UpdateTaskRequest = WithOptionalKeepBreaks<components["schemas"]["UpdateTaskRequest"]>;
|
|
4179
4327
|
type TaskSectionsResponse = components["schemas"]["TaskSectionsResponse"];
|
|
4180
4328
|
type Project = components["schemas"]["Project"];
|
|
4181
|
-
type CreateProjectRequest = components["schemas"]["CreateProjectRequest"]
|
|
4182
|
-
type UpdateProjectRequest = components["schemas"]["UpdateProjectRequest"]
|
|
4329
|
+
type CreateProjectRequest = WithOptionalKeepBreaks<components["schemas"]["CreateProjectRequest"]>;
|
|
4330
|
+
type UpdateProjectRequest = WithOptionalKeepBreaks<components["schemas"]["UpdateProjectRequest"]>;
|
|
4183
4331
|
type Note = components["schemas"]["Note"];
|
|
4184
|
-
type CreateNoteRequest = components["schemas"]["CreateNoteRequest"]
|
|
4185
|
-
type UpdateNoteRequest = components["schemas"]["UpdateNoteRequest"]
|
|
4332
|
+
type CreateNoteRequest = WithOptionalKeepBreaks<components["schemas"]["CreateNoteRequest"]>;
|
|
4333
|
+
type UpdateNoteRequest = WithOptionalKeepBreaks<components["schemas"]["UpdateNoteRequest"]>;
|
|
4186
4334
|
type Session = components["schemas"]["Session"];
|
|
4187
4335
|
type SessionWithActivities = components["schemas"]["SessionWithActivities"];
|
|
4188
4336
|
type SessionActivity = components["schemas"]["SessionActivity"];
|
|
@@ -4230,6 +4378,7 @@ type SubtaskListParams = Omit<NonNullable<paths["/tasks/{id}/subtasks"]["get"]["
|
|
|
4230
4378
|
type ProjectListParams = NonNullable<paths["/projects"]["get"]["parameters"]["query"]>;
|
|
4231
4379
|
type NoteListParams = NonNullable<paths["/notes"]["get"]["parameters"]["query"]>;
|
|
4232
4380
|
type SessionListParams = NonNullable<paths["/sessions"]["get"]["parameters"]["query"]>;
|
|
4381
|
+
type ActivityListParams = NonNullable<paths["/sessions/activities"]["get"]["parameters"]["query"]>;
|
|
4233
4382
|
type WebhookListParams = Omit<NonNullable<paths["/webhooks"]["get"]["parameters"]["query"]>, "isActive"> & {
|
|
4234
4383
|
isActive?: BooleanParam;
|
|
4235
4384
|
};
|
|
@@ -4327,6 +4476,8 @@ declare const createLocuClient: (config: LocuClientConfig) => {
|
|
|
4327
4476
|
delete: (id: string) => Promise<{
|
|
4328
4477
|
success: boolean;
|
|
4329
4478
|
}>;
|
|
4479
|
+
/** List all activities with optional filters */
|
|
4480
|
+
listActivities: (params?: ActivityListParams) => Promise<PaginatedResponse<SessionActivity>>;
|
|
4330
4481
|
activities: {
|
|
4331
4482
|
/** List activities for a session */
|
|
4332
4483
|
list: (sessionId: string) => Promise<{
|
package/dist/index.js
CHANGED
|
@@ -159,6 +159,8 @@ var createLocuClient = (config) => {
|
|
|
159
159
|
update: (id, data) => request("PATCH", `/sessions/${id}`, data),
|
|
160
160
|
/** Delete a session */
|
|
161
161
|
delete: (id) => request("DELETE", `/sessions/${id}`),
|
|
162
|
+
/** List all activities with optional filters */
|
|
163
|
+
listActivities: (params = {}) => request("GET", `/sessions/activities${buildQueryString(params)}`),
|
|
162
164
|
// Activities
|
|
163
165
|
activities: {
|
|
164
166
|
/** List activities for a session */
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["// Client\nexport { createLocuClient, LocuApiError } from \"./client\"\nexport type { LocuClientConfig, LocuClient } from \"./client\"\n\n// Webhook utilities\nexport {\n verifyWebhookSignature,\n parseWebhookSignature,\n parseWebhookPayload,\n generateWebhookSignature,\n} from \"./webhook\"\nexport type {\n WebhookSignatureResult,\n ParsedWebhookSignature,\n VerifyWebhookOptions,\n} from \"./webhook\"\n\nexport type * from \"./generated/api\"\n","import type {\n ApiError,\n CreateActivityRequest,\n CreateNoteRequest,\n CreateProjectRequest,\n CreateSessionRequest,\n CreateTaskRequest,\n CreateWebhookRequest,\n MeResponse,\n Note,\n NoteListParams,\n PaginatedResponse,\n PaginationParams,\n Project,\n ProjectListParams,\n Session,\n SessionActivity,\n SessionListParams,\n SessionWithActivities,\n StartTimerRequest,\n StopTimerResponse,\n SubtaskListParams,\n Task,\n TaskListParams,\n TaskSectionsParams,\n TaskSectionsResponse,\n TimerState,\n UpdateActivityRequest,\n UpdateNoteRequest,\n UpdateProjectRequest,\n UpdateSessionRequest,\n UpdateTaskRequest,\n UpdateWebhookRequest,\n Webhook,\n WebhookDelivery,\n WebhookListParams,\n WebhookWithSecret,\n} from \"./types\"\n\nexport type LocuClientConfig = {\n /** API base URL (defaults to https://api.locu.app/api/v1) */\n baseUrl?: string\n /** Personal Access Token for authentication */\n token: string\n /** Custom fetch implementation (defaults to global fetch) */\n fetch?: typeof fetch\n}\n\nexport class LocuApiError extends Error {\n status: number\n code?: string\n\n constructor(message: string, status: number, code?: string) {\n super(message)\n this.name = \"LocuApiError\"\n this.status = status\n this.code = code\n }\n}\n\nconst buildQueryString = (params: Record<string, unknown>): string => {\n const searchParams = new URLSearchParams()\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n searchParams.set(key, String(value))\n }\n }\n const qs = searchParams.toString()\n return qs ? `?${qs}` : \"\"\n}\n\nexport const createLocuClient = (config: LocuClientConfig) => {\n const baseUrl = config.baseUrl || \"https://api.locu.app/api/v1\"\n const fetchFn = config.fetch || fetch\n\n const request = async <T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> => {\n const url = `${baseUrl}${path}`\n const headers: Record<string, string> = {\n Authorization: `Bearer ${config.token}`,\n \"Content-Type\": \"application/json\",\n }\n\n const response = await fetchFn(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n })\n\n if (!response.ok) {\n let errorData: ApiError | null = null\n try {\n errorData = await response.json()\n } catch {\n // Ignore JSON parse errors\n }\n throw new LocuApiError(\n errorData?.message || `Request failed with status ${response.status}`,\n response.status,\n errorData?.code\n )\n }\n\n // Handle 204 No Content\n if (response.status === 204) {\n return undefined as T\n }\n\n return response.json()\n }\n\n return {\n // ============ Me ============\n me: {\n /** Get current me */\n get: (): Promise<MeResponse> => request(\"GET\", \"/me\"),\n },\n\n // ============ Timer ============\n timer: {\n /** Get current timer */\n get: (): Promise<TimerState> => request(\"GET\", \"/timer\"),\n\n /** Start a new timer */\n start: (data?: StartTimerRequest): Promise<TimerState> =>\n request(\"POST\", \"/timer/start\", data),\n\n /** Pause the running timer */\n pause: (): Promise<TimerState> => request(\"POST\", \"/timer/pause\"),\n\n /** Resume a paused timer */\n continue: (): Promise<TimerState> => request(\"POST\", \"/timer/continue\"),\n\n /** Stop timer and save sessions */\n stop: (): Promise<StopTimerResponse> => request(\"POST\", \"/timer/stop\"),\n\n /** Cancel timer without saving sessions */\n cancel: (): Promise<TimerState> => request(\"DELETE\", \"/timer\"),\n },\n\n // ============ Tasks ============\n tasks: {\n /** List all tasks */\n list: (params: TaskListParams = {}): Promise<PaginatedResponse<Task>> =>\n request(\"GET\", `/tasks${buildQueryString(params)}`),\n\n /** Get a single task by ID */\n get: (id: string): Promise<Task> => request(\"GET\", `/tasks/${id}`),\n\n /** Create a new task */\n create: (data: CreateTaskRequest): Promise<Task> =>\n request(\"POST\", \"/tasks\", data),\n\n /** Update an existing task */\n update: (id: string, data: UpdateTaskRequest): Promise<Task> =>\n request(\"PATCH\", `/tasks/${id}`, data),\n\n /** Delete a task */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/tasks/${id}`),\n\n /** Get tasks organized by section (today, sooner, later) */\n sections: (\n params: TaskSectionsParams = {}\n ): Promise<TaskSectionsResponse> =>\n request(\"GET\", `/tasks/sections${buildQueryString(params)}`),\n\n /** List subtasks for a task */\n subtasks: (\n id: string,\n params: SubtaskListParams = {}\n ): Promise<PaginatedResponse<Task>> =>\n request(\"GET\", `/tasks/${id}/subtasks${buildQueryString(params)}`),\n\n /** Create a subtask under a parent task */\n createSubtask: (\n parentId: string,\n data: Omit<CreateTaskRequest, \"parentId\" | \"section\">\n ): Promise<Task> => request(\"POST\", \"/tasks\", { ...data, parentId }),\n },\n\n // ============ Projects ============\n projects: {\n /** List all projects */\n list: (\n params: ProjectListParams = {}\n ): Promise<PaginatedResponse<Project>> =>\n request(\"GET\", `/projects${buildQueryString(params)}`),\n\n /** Get a single project by ID */\n get: (id: string): Promise<Project> => request(\"GET\", `/projects/${id}`),\n\n /** Create a new project */\n create: (data: CreateProjectRequest): Promise<Project> =>\n request(\"POST\", \"/projects\", data),\n\n /** Update an existing project */\n update: (id: string, data: UpdateProjectRequest): Promise<Project> =>\n request(\"PATCH\", `/projects/${id}`, data),\n\n /** Delete a project */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/projects/${id}`),\n },\n\n // ============ Notes ============\n notes: {\n /** List all notes */\n list: (params: NoteListParams = {}): Promise<PaginatedResponse<Note>> =>\n request(\"GET\", `/notes${buildQueryString(params)}`),\n\n /** Get a single note by ID */\n get: (id: string): Promise<Note> => request(\"GET\", `/notes/${id}`),\n\n /** Create a new note */\n create: (data: CreateNoteRequest): Promise<Note> =>\n request(\"POST\", \"/notes\", data),\n\n /** Update an existing note */\n update: (id: string, data: UpdateNoteRequest): Promise<Note> =>\n request(\"PATCH\", `/notes/${id}`, data),\n\n /** Delete a note */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/notes/${id}`),\n },\n\n // ============ Sessions ============\n sessions: {\n /** List all sessions */\n list: (\n params: SessionListParams = {}\n ): Promise<PaginatedResponse<SessionWithActivities>> =>\n request(\"GET\", `/sessions${buildQueryString(params)}`),\n\n /** Get a single session by ID */\n get: (id: string): Promise<SessionWithActivities> =>\n request(\"GET\", `/sessions/${id}`),\n\n /** Create a new session */\n create: (data: CreateSessionRequest): Promise<Session> =>\n request(\"POST\", \"/sessions\", data),\n\n /** Update an existing session */\n update: (id: string, data: UpdateSessionRequest): Promise<Session> =>\n request(\"PATCH\", `/sessions/${id}`, data),\n\n /** Delete a session */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/sessions/${id}`),\n\n // Activities\n activities: {\n /** List activities for a session */\n list: (sessionId: string): Promise<{ data: SessionActivity[] }> =>\n request(\"GET\", `/sessions/${sessionId}/activities`),\n\n /** Create a new activitie */\n create: (\n sessionId: string,\n data: CreateActivityRequest\n ): Promise<SessionActivity> =>\n request(\"POST\", `/sessions/${sessionId}/activities`, data),\n\n /** Update an activitie */\n update: (\n sessionId: string,\n activityId: string,\n data: UpdateActivityRequest\n ): Promise<SessionActivity> =>\n request(\n \"PATCH\",\n `/sessions/${sessionId}/activities/${activityId}`,\n data\n ),\n\n /** Delete an activitie */\n delete: (\n sessionId: string,\n activityId: string\n ): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/sessions/${sessionId}/activities/${activityId}`),\n },\n },\n\n // ============ Webhooks ============\n webhooks: {\n /** List all webhooks */\n list: (\n params: WebhookListParams = {}\n ): Promise<PaginatedResponse<Webhook>> =>\n request(\"GET\", `/webhooks${buildQueryString(params)}`),\n\n /** Get a single webhook by ID */\n get: (id: string): Promise<Webhook> => request(\"GET\", `/webhooks/${id}`),\n\n /** Create a new webhook */\n create: (data: CreateWebhookRequest): Promise<WebhookWithSecret> =>\n request(\"POST\", \"/webhooks\", data),\n\n /** Update an existing webhook */\n update: (id: string, data: UpdateWebhookRequest): Promise<Webhook> =>\n request(\"PATCH\", `/webhooks/${id}`, data),\n\n /** Delete a webhook */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/webhooks/${id}`),\n\n /** Rotate webhook secret */\n rotateSecret: (id: string): Promise<{ secret: string }> =>\n request(\"POST\", `/webhooks/${id}/rotate-secret`),\n\n /** List deliveries for a webhook */\n deliveries: (\n id: string,\n params: PaginationParams = {}\n ): Promise<PaginatedResponse<WebhookDelivery>> =>\n request(\"GET\", `/webhooks/${id}/deliveries${buildQueryString(params)}`),\n },\n }\n}\n\nexport type LocuClient = ReturnType<typeof createLocuClient>\n","import { createHmac, timingSafeEqual } from \"crypto\"\nimport type { WebhookPayload } from \"./types\"\n\nexport type WebhookSignatureResult =\n | { valid: true }\n | { valid: false; error: string }\n\nexport type ParsedWebhookSignature = {\n timestamp: number\n signature: string\n}\n\nexport type VerifyWebhookOptions = {\n /** Maximum age of signature in seconds (default: 300 = 5 minutes) */\n maxAge?: number\n}\n\n/**\n * Parse a webhook signature header into its components.\n *\n * The signature header format is: `t=<timestamp>,v1=<hex_signature>`\n *\n * @param signatureHeader - The X-Webhook-Signature header value\n * @returns Parsed timestamp and signature, or null if invalid format\n *\n * @example\n * ```typescript\n * const parsed = parseWebhookSignature(request.headers['x-webhook-signature'])\n * if (parsed) {\n * console.log('Timestamp:', parsed.timestamp)\n * console.log('Signature:', parsed.signature)\n * }\n * ```\n */\nexport const parseWebhookSignature = (\n signatureHeader: string\n): ParsedWebhookSignature | null => {\n const parts = signatureHeader.split(\",\")\n\n let timestamp: number | null = null\n let signature: string | null = null\n\n for (const part of parts) {\n const eqIndex = part.indexOf(\"=\")\n if (eqIndex === -1) continue\n const key = part.slice(0, eqIndex)\n const value = part.slice(eqIndex + 1)\n if (key === \"t\") {\n timestamp = parseInt(value, 10)\n } else if (key === \"v1\") {\n signature = value\n }\n }\n\n if (timestamp === null || signature === null || isNaN(timestamp)) {\n return null\n }\n\n return { timestamp, signature }\n}\n\n/**\n * Verify a webhook signature using HMAC-SHA256.\n *\n * This function verifies that a webhook payload was signed by Locu using your webhook secret.\n * It also checks that the signature timestamp is not too old to prevent replay attacks.\n *\n * @param secret - Your webhook secret (starts with `whsec_`)\n * @param signatureHeader - The X-Webhook-Signature header value\n * @param body - The raw request body as a string\n * @param options - Optional verification settings\n * @returns Object with `valid: true` if valid, or `valid: false` with an error message\n *\n * @example\n * ```typescript\n * import { verifyWebhookSignature } from '@locu/api-client'\n *\n * app.post('/webhooks/locu', (req, res) => {\n * const result = verifyWebhookSignature(\n * process.env.LOCU_WEBHOOK_SECRET,\n * req.headers['x-webhook-signature'],\n * req.body, // raw body string\n * { maxAge: 300 } // 5 minutes\n * )\n *\n * if (!result.valid) {\n * return res.status(401).json({ error: result.error })\n * }\n *\n * // Process the webhook\n * const payload = JSON.parse(req.body)\n * console.log('Received event:', payload.event)\n * })\n * ```\n */\nexport const verifyWebhookSignature = (\n secret: string,\n signatureHeader: string,\n body: string,\n options?: VerifyWebhookOptions\n): WebhookSignatureResult => {\n const parsed = parseWebhookSignature(signatureHeader)\n\n if (!parsed) {\n return { valid: false, error: \"Invalid signature format\" }\n }\n\n const { timestamp, signature } = parsed\n\n // Check timestamp age if maxAge is specified\n if (options?.maxAge !== undefined) {\n const now = Math.floor(Date.now() / 1000)\n const age = now - timestamp\n\n if (age > options.maxAge) {\n return { valid: false, error: \"Signature timestamp too old\" }\n }\n\n if (age < -60) {\n // Allow 1 minute clock skew into the future\n return { valid: false, error: \"Signature timestamp in the future\" }\n }\n }\n\n // Compute expected signature\n const signaturePayload = `${timestamp}.${body}`\n const expectedSignature = createHmac(\"sha256\", secret)\n .update(signaturePayload)\n .digest(\"hex\")\n\n // Use timing-safe comparison to prevent timing attacks\n const signatureBuffer = Buffer.from(signature, \"hex\")\n const expectedBuffer = Buffer.from(expectedSignature, \"hex\")\n\n if (signatureBuffer.length !== expectedBuffer.length) {\n return { valid: false, error: \"Invalid signature\" }\n }\n\n const isValid = timingSafeEqual(signatureBuffer, expectedBuffer)\n\n if (!isValid) {\n return { valid: false, error: \"Invalid signature\" }\n }\n\n return { valid: true }\n}\n\n/**\n * Parse a webhook payload from a JSON string.\n *\n * @param body - The raw request body as a JSON string\n * @returns The parsed webhook payload\n *\n * @example\n * ```typescript\n * import { parseWebhookPayload, TaskWebhookPayload } from '@locu/api-client'\n *\n * const payload = parseWebhookPayload<TaskWebhookPayload>(req.body)\n * console.log('Event:', payload.event) // e.g., \"task.created\"\n * console.log('Task name:', payload.data.name)\n * ```\n */\nexport const parseWebhookPayload = <T = unknown>(\n body: string\n): WebhookPayload<T> => {\n return JSON.parse(body) as WebhookPayload<T>\n}\n\n/**\n * Generate a webhook signature for testing purposes.\n *\n * This is useful for testing your webhook handlers locally.\n *\n * @param secret - Your webhook secret\n * @param timestamp - Unix timestamp in seconds\n * @param body - The request body as a string\n * @returns The signature header value in format `t=<timestamp>,v1=<signature>`\n *\n * @example\n * ```typescript\n * import { generateWebhookSignature } from '@locu/api-client'\n *\n * const body = JSON.stringify({ event: 'task.created', timestamp: '...', data: {...} })\n * const signature = generateWebhookSignature('whsec_...', Math.floor(Date.now() / 1000), body)\n * // Use signature for testing your webhook handler\n * ```\n */\nexport const generateWebhookSignature = (\n secret: string,\n timestamp: number,\n body: string\n): string => {\n const signaturePayload = `${timestamp}.${body}`\n const signature = createHmac(\"sha256\", secret)\n .update(signaturePayload)\n .digest(\"hex\")\n return `t=${timestamp},v1=${signature}`\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["// Client\nexport { createLocuClient, LocuApiError } from \"./client\"\nexport type { LocuClientConfig, LocuClient } from \"./client\"\n\n// Webhook utilities\nexport {\n verifyWebhookSignature,\n parseWebhookSignature,\n parseWebhookPayload,\n generateWebhookSignature,\n} from \"./webhook\"\nexport type {\n WebhookSignatureResult,\n ParsedWebhookSignature,\n VerifyWebhookOptions,\n} from \"./webhook\"\n\nexport type * from \"./generated/api\"\n","import type {\n ActivityListParams,\n ApiError,\n CreateActivityRequest,\n CreateNoteRequest,\n CreateProjectRequest,\n CreateSessionRequest,\n CreateTaskRequest,\n CreateWebhookRequest,\n MeResponse,\n Note,\n NoteListParams,\n PaginatedResponse,\n PaginationParams,\n Project,\n ProjectListParams,\n Session,\n SessionActivity,\n SessionListParams,\n SessionWithActivities,\n StartTimerRequest,\n StopTimerResponse,\n SubtaskListParams,\n Task,\n TaskListParams,\n TaskSectionsParams,\n TaskSectionsResponse,\n TimerState,\n UpdateActivityRequest,\n UpdateNoteRequest,\n UpdateProjectRequest,\n UpdateSessionRequest,\n UpdateTaskRequest,\n UpdateWebhookRequest,\n Webhook,\n WebhookDelivery,\n WebhookListParams,\n WebhookWithSecret,\n} from \"./types\"\n\nexport type LocuClientConfig = {\n /** API base URL (defaults to https://api.locu.app/api/v1) */\n baseUrl?: string\n /** Personal Access Token for authentication */\n token: string\n /** Custom fetch implementation (defaults to global fetch) */\n fetch?: typeof fetch\n}\n\nexport class LocuApiError extends Error {\n status: number\n code?: string\n\n constructor(message: string, status: number, code?: string) {\n super(message)\n this.name = \"LocuApiError\"\n this.status = status\n this.code = code\n }\n}\n\nconst buildQueryString = (params: Record<string, unknown>): string => {\n const searchParams = new URLSearchParams()\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n searchParams.set(key, String(value))\n }\n }\n const qs = searchParams.toString()\n return qs ? `?${qs}` : \"\"\n}\n\nexport const createLocuClient = (config: LocuClientConfig) => {\n const baseUrl = config.baseUrl || \"https://api.locu.app/api/v1\"\n const fetchFn = config.fetch || fetch\n\n const request = async <T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> => {\n const url = `${baseUrl}${path}`\n const headers: Record<string, string> = {\n Authorization: `Bearer ${config.token}`,\n \"Content-Type\": \"application/json\",\n }\n\n const response = await fetchFn(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n })\n\n if (!response.ok) {\n let errorData: ApiError | null = null\n try {\n errorData = await response.json()\n } catch {\n // Ignore JSON parse errors\n }\n throw new LocuApiError(\n errorData?.message || `Request failed with status ${response.status}`,\n response.status,\n errorData?.code\n )\n }\n\n // Handle 204 No Content\n if (response.status === 204) {\n return undefined as T\n }\n\n return response.json()\n }\n\n return {\n // ============ Me ============\n me: {\n /** Get current me */\n get: (): Promise<MeResponse> => request(\"GET\", \"/me\"),\n },\n\n // ============ Timer ============\n timer: {\n /** Get current timer */\n get: (): Promise<TimerState> => request(\"GET\", \"/timer\"),\n\n /** Start a new timer */\n start: (data?: StartTimerRequest): Promise<TimerState> =>\n request(\"POST\", \"/timer/start\", data),\n\n /** Pause the running timer */\n pause: (): Promise<TimerState> => request(\"POST\", \"/timer/pause\"),\n\n /** Resume a paused timer */\n continue: (): Promise<TimerState> => request(\"POST\", \"/timer/continue\"),\n\n /** Stop timer and save sessions */\n stop: (): Promise<StopTimerResponse> => request(\"POST\", \"/timer/stop\"),\n\n /** Cancel timer without saving sessions */\n cancel: (): Promise<TimerState> => request(\"DELETE\", \"/timer\"),\n },\n\n // ============ Tasks ============\n tasks: {\n /** List all tasks */\n list: (params: TaskListParams = {}): Promise<PaginatedResponse<Task>> =>\n request(\"GET\", `/tasks${buildQueryString(params)}`),\n\n /** Get a single task by ID */\n get: (id: string): Promise<Task> => request(\"GET\", `/tasks/${id}`),\n\n /** Create a new task */\n create: (data: CreateTaskRequest): Promise<Task> =>\n request(\"POST\", \"/tasks\", data),\n\n /** Update an existing task */\n update: (id: string, data: UpdateTaskRequest): Promise<Task> =>\n request(\"PATCH\", `/tasks/${id}`, data),\n\n /** Delete a task */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/tasks/${id}`),\n\n /** Get tasks organized by section (today, sooner, later) */\n sections: (\n params: TaskSectionsParams = {}\n ): Promise<TaskSectionsResponse> =>\n request(\"GET\", `/tasks/sections${buildQueryString(params)}`),\n\n /** List subtasks for a task */\n subtasks: (\n id: string,\n params: SubtaskListParams = {}\n ): Promise<PaginatedResponse<Task>> =>\n request(\"GET\", `/tasks/${id}/subtasks${buildQueryString(params)}`),\n\n /** Create a subtask under a parent task */\n createSubtask: (\n parentId: string,\n data: Omit<CreateTaskRequest, \"parentId\" | \"section\">\n ): Promise<Task> => request(\"POST\", \"/tasks\", { ...data, parentId }),\n },\n\n // ============ Projects ============\n projects: {\n /** List all projects */\n list: (\n params: ProjectListParams = {}\n ): Promise<PaginatedResponse<Project>> =>\n request(\"GET\", `/projects${buildQueryString(params)}`),\n\n /** Get a single project by ID */\n get: (id: string): Promise<Project> => request(\"GET\", `/projects/${id}`),\n\n /** Create a new project */\n create: (data: CreateProjectRequest): Promise<Project> =>\n request(\"POST\", \"/projects\", data),\n\n /** Update an existing project */\n update: (id: string, data: UpdateProjectRequest): Promise<Project> =>\n request(\"PATCH\", `/projects/${id}`, data),\n\n /** Delete a project */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/projects/${id}`),\n },\n\n // ============ Notes ============\n notes: {\n /** List all notes */\n list: (params: NoteListParams = {}): Promise<PaginatedResponse<Note>> =>\n request(\"GET\", `/notes${buildQueryString(params)}`),\n\n /** Get a single note by ID */\n get: (id: string): Promise<Note> => request(\"GET\", `/notes/${id}`),\n\n /** Create a new note */\n create: (data: CreateNoteRequest): Promise<Note> =>\n request(\"POST\", \"/notes\", data),\n\n /** Update an existing note */\n update: (id: string, data: UpdateNoteRequest): Promise<Note> =>\n request(\"PATCH\", `/notes/${id}`, data),\n\n /** Delete a note */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/notes/${id}`),\n },\n\n // ============ Sessions ============\n sessions: {\n /** List all sessions */\n list: (\n params: SessionListParams = {}\n ): Promise<PaginatedResponse<SessionWithActivities>> =>\n request(\"GET\", `/sessions${buildQueryString(params)}`),\n\n /** Get a single session by ID */\n get: (id: string): Promise<SessionWithActivities> =>\n request(\"GET\", `/sessions/${id}`),\n\n /** Create a new session */\n create: (data: CreateSessionRequest): Promise<Session> =>\n request(\"POST\", \"/sessions\", data),\n\n /** Update an existing session */\n update: (id: string, data: UpdateSessionRequest): Promise<Session> =>\n request(\"PATCH\", `/sessions/${id}`, data),\n\n /** Delete a session */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/sessions/${id}`),\n\n /** List all activities with optional filters */\n listActivities: (\n params: ActivityListParams = {}\n ): Promise<PaginatedResponse<SessionActivity>> =>\n request(\"GET\", `/sessions/activities${buildQueryString(params)}`),\n\n // Activities\n activities: {\n /** List activities for a session */\n list: (sessionId: string): Promise<{ data: SessionActivity[] }> =>\n request(\"GET\", `/sessions/${sessionId}/activities`),\n\n /** Create a new activitie */\n create: (\n sessionId: string,\n data: CreateActivityRequest\n ): Promise<SessionActivity> =>\n request(\"POST\", `/sessions/${sessionId}/activities`, data),\n\n /** Update an activitie */\n update: (\n sessionId: string,\n activityId: string,\n data: UpdateActivityRequest\n ): Promise<SessionActivity> =>\n request(\n \"PATCH\",\n `/sessions/${sessionId}/activities/${activityId}`,\n data\n ),\n\n /** Delete an activitie */\n delete: (\n sessionId: string,\n activityId: string\n ): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/sessions/${sessionId}/activities/${activityId}`),\n },\n },\n\n // ============ Webhooks ============\n webhooks: {\n /** List all webhooks */\n list: (\n params: WebhookListParams = {}\n ): Promise<PaginatedResponse<Webhook>> =>\n request(\"GET\", `/webhooks${buildQueryString(params)}`),\n\n /** Get a single webhook by ID */\n get: (id: string): Promise<Webhook> => request(\"GET\", `/webhooks/${id}`),\n\n /** Create a new webhook */\n create: (data: CreateWebhookRequest): Promise<WebhookWithSecret> =>\n request(\"POST\", \"/webhooks\", data),\n\n /** Update an existing webhook */\n update: (id: string, data: UpdateWebhookRequest): Promise<Webhook> =>\n request(\"PATCH\", `/webhooks/${id}`, data),\n\n /** Delete a webhook */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/webhooks/${id}`),\n\n /** Rotate webhook secret */\n rotateSecret: (id: string): Promise<{ secret: string }> =>\n request(\"POST\", `/webhooks/${id}/rotate-secret`),\n\n /** List deliveries for a webhook */\n deliveries: (\n id: string,\n params: PaginationParams = {}\n ): Promise<PaginatedResponse<WebhookDelivery>> =>\n request(\"GET\", `/webhooks/${id}/deliveries${buildQueryString(params)}`),\n },\n }\n}\n\nexport type LocuClient = ReturnType<typeof createLocuClient>\n","import { createHmac, timingSafeEqual } from \"crypto\"\nimport type { WebhookPayload } from \"./types\"\n\nexport type WebhookSignatureResult =\n | { valid: true }\n | { valid: false; error: string }\n\nexport type ParsedWebhookSignature = {\n timestamp: number\n signature: string\n}\n\nexport type VerifyWebhookOptions = {\n /** Maximum age of signature in seconds (default: 300 = 5 minutes) */\n maxAge?: number\n}\n\n/**\n * Parse a webhook signature header into its components.\n *\n * The signature header format is: `t=<timestamp>,v1=<hex_signature>`\n *\n * @param signatureHeader - The X-Webhook-Signature header value\n * @returns Parsed timestamp and signature, or null if invalid format\n *\n * @example\n * ```typescript\n * const parsed = parseWebhookSignature(request.headers['x-webhook-signature'])\n * if (parsed) {\n * console.log('Timestamp:', parsed.timestamp)\n * console.log('Signature:', parsed.signature)\n * }\n * ```\n */\nexport const parseWebhookSignature = (\n signatureHeader: string\n): ParsedWebhookSignature | null => {\n const parts = signatureHeader.split(\",\")\n\n let timestamp: number | null = null\n let signature: string | null = null\n\n for (const part of parts) {\n const eqIndex = part.indexOf(\"=\")\n if (eqIndex === -1) continue\n const key = part.slice(0, eqIndex)\n const value = part.slice(eqIndex + 1)\n if (key === \"t\") {\n timestamp = parseInt(value, 10)\n } else if (key === \"v1\") {\n signature = value\n }\n }\n\n if (timestamp === null || signature === null || isNaN(timestamp)) {\n return null\n }\n\n return { timestamp, signature }\n}\n\n/**\n * Verify a webhook signature using HMAC-SHA256.\n *\n * This function verifies that a webhook payload was signed by Locu using your webhook secret.\n * It also checks that the signature timestamp is not too old to prevent replay attacks.\n *\n * @param secret - Your webhook secret (starts with `whsec_`)\n * @param signatureHeader - The X-Webhook-Signature header value\n * @param body - The raw request body as a string\n * @param options - Optional verification settings\n * @returns Object with `valid: true` if valid, or `valid: false` with an error message\n *\n * @example\n * ```typescript\n * import { verifyWebhookSignature } from '@locu/api-client'\n *\n * app.post('/webhooks/locu', (req, res) => {\n * const result = verifyWebhookSignature(\n * process.env.LOCU_WEBHOOK_SECRET,\n * req.headers['x-webhook-signature'],\n * req.body, // raw body string\n * { maxAge: 300 } // 5 minutes\n * )\n *\n * if (!result.valid) {\n * return res.status(401).json({ error: result.error })\n * }\n *\n * // Process the webhook\n * const payload = JSON.parse(req.body)\n * console.log('Received event:', payload.event)\n * })\n * ```\n */\nexport const verifyWebhookSignature = (\n secret: string,\n signatureHeader: string,\n body: string,\n options?: VerifyWebhookOptions\n): WebhookSignatureResult => {\n const parsed = parseWebhookSignature(signatureHeader)\n\n if (!parsed) {\n return { valid: false, error: \"Invalid signature format\" }\n }\n\n const { timestamp, signature } = parsed\n\n // Check timestamp age if maxAge is specified\n if (options?.maxAge !== undefined) {\n const now = Math.floor(Date.now() / 1000)\n const age = now - timestamp\n\n if (age > options.maxAge) {\n return { valid: false, error: \"Signature timestamp too old\" }\n }\n\n if (age < -60) {\n // Allow 1 minute clock skew into the future\n return { valid: false, error: \"Signature timestamp in the future\" }\n }\n }\n\n // Compute expected signature\n const signaturePayload = `${timestamp}.${body}`\n const expectedSignature = createHmac(\"sha256\", secret)\n .update(signaturePayload)\n .digest(\"hex\")\n\n // Use timing-safe comparison to prevent timing attacks\n const signatureBuffer = Buffer.from(signature, \"hex\")\n const expectedBuffer = Buffer.from(expectedSignature, \"hex\")\n\n if (signatureBuffer.length !== expectedBuffer.length) {\n return { valid: false, error: \"Invalid signature\" }\n }\n\n const isValid = timingSafeEqual(signatureBuffer, expectedBuffer)\n\n if (!isValid) {\n return { valid: false, error: \"Invalid signature\" }\n }\n\n return { valid: true }\n}\n\n/**\n * Parse a webhook payload from a JSON string.\n *\n * @param body - The raw request body as a JSON string\n * @returns The parsed webhook payload\n *\n * @example\n * ```typescript\n * import { parseWebhookPayload, TaskWebhookPayload } from '@locu/api-client'\n *\n * const payload = parseWebhookPayload<TaskWebhookPayload>(req.body)\n * console.log('Event:', payload.event) // e.g., \"task.created\"\n * console.log('Task name:', payload.data.name)\n * ```\n */\nexport const parseWebhookPayload = <T = unknown>(\n body: string\n): WebhookPayload<T> => {\n return JSON.parse(body) as WebhookPayload<T>\n}\n\n/**\n * Generate a webhook signature for testing purposes.\n *\n * This is useful for testing your webhook handlers locally.\n *\n * @param secret - Your webhook secret\n * @param timestamp - Unix timestamp in seconds\n * @param body - The request body as a string\n * @returns The signature header value in format `t=<timestamp>,v1=<signature>`\n *\n * @example\n * ```typescript\n * import { generateWebhookSignature } from '@locu/api-client'\n *\n * const body = JSON.stringify({ event: 'task.created', timestamp: '...', data: {...} })\n * const signature = generateWebhookSignature('whsec_...', Math.floor(Date.now() / 1000), body)\n * // Use signature for testing your webhook handler\n * ```\n */\nexport const generateWebhookSignature = (\n secret: string,\n timestamp: number,\n body: string\n): string => {\n const signaturePayload = `${timestamp}.${body}`\n const signature = createHmac(\"sha256\", secret)\n .update(signaturePayload)\n .digest(\"hex\")\n return `t=${timestamp},v1=${signature}`\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -128,6 +128,8 @@ var createLocuClient = (config) => {
|
|
|
128
128
|
update: (id, data) => request("PATCH", `/sessions/${id}`, data),
|
|
129
129
|
/** Delete a session */
|
|
130
130
|
delete: (id) => request("DELETE", `/sessions/${id}`),
|
|
131
|
+
/** List all activities with optional filters */
|
|
132
|
+
listActivities: (params = {}) => request("GET", `/sessions/activities${buildQueryString(params)}`),
|
|
131
133
|
// Activities
|
|
132
134
|
activities: {
|
|
133
135
|
/** List activities for a session */
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/webhook.ts"],"sourcesContent":["import type {\n ApiError,\n CreateActivityRequest,\n CreateNoteRequest,\n CreateProjectRequest,\n CreateSessionRequest,\n CreateTaskRequest,\n CreateWebhookRequest,\n MeResponse,\n Note,\n NoteListParams,\n PaginatedResponse,\n PaginationParams,\n Project,\n ProjectListParams,\n Session,\n SessionActivity,\n SessionListParams,\n SessionWithActivities,\n StartTimerRequest,\n StopTimerResponse,\n SubtaskListParams,\n Task,\n TaskListParams,\n TaskSectionsParams,\n TaskSectionsResponse,\n TimerState,\n UpdateActivityRequest,\n UpdateNoteRequest,\n UpdateProjectRequest,\n UpdateSessionRequest,\n UpdateTaskRequest,\n UpdateWebhookRequest,\n Webhook,\n WebhookDelivery,\n WebhookListParams,\n WebhookWithSecret,\n} from \"./types\"\n\nexport type LocuClientConfig = {\n /** API base URL (defaults to https://api.locu.app/api/v1) */\n baseUrl?: string\n /** Personal Access Token for authentication */\n token: string\n /** Custom fetch implementation (defaults to global fetch) */\n fetch?: typeof fetch\n}\n\nexport class LocuApiError extends Error {\n status: number\n code?: string\n\n constructor(message: string, status: number, code?: string) {\n super(message)\n this.name = \"LocuApiError\"\n this.status = status\n this.code = code\n }\n}\n\nconst buildQueryString = (params: Record<string, unknown>): string => {\n const searchParams = new URLSearchParams()\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n searchParams.set(key, String(value))\n }\n }\n const qs = searchParams.toString()\n return qs ? `?${qs}` : \"\"\n}\n\nexport const createLocuClient = (config: LocuClientConfig) => {\n const baseUrl = config.baseUrl || \"https://api.locu.app/api/v1\"\n const fetchFn = config.fetch || fetch\n\n const request = async <T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> => {\n const url = `${baseUrl}${path}`\n const headers: Record<string, string> = {\n Authorization: `Bearer ${config.token}`,\n \"Content-Type\": \"application/json\",\n }\n\n const response = await fetchFn(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n })\n\n if (!response.ok) {\n let errorData: ApiError | null = null\n try {\n errorData = await response.json()\n } catch {\n // Ignore JSON parse errors\n }\n throw new LocuApiError(\n errorData?.message || `Request failed with status ${response.status}`,\n response.status,\n errorData?.code\n )\n }\n\n // Handle 204 No Content\n if (response.status === 204) {\n return undefined as T\n }\n\n return response.json()\n }\n\n return {\n // ============ Me ============\n me: {\n /** Get current me */\n get: (): Promise<MeResponse> => request(\"GET\", \"/me\"),\n },\n\n // ============ Timer ============\n timer: {\n /** Get current timer */\n get: (): Promise<TimerState> => request(\"GET\", \"/timer\"),\n\n /** Start a new timer */\n start: (data?: StartTimerRequest): Promise<TimerState> =>\n request(\"POST\", \"/timer/start\", data),\n\n /** Pause the running timer */\n pause: (): Promise<TimerState> => request(\"POST\", \"/timer/pause\"),\n\n /** Resume a paused timer */\n continue: (): Promise<TimerState> => request(\"POST\", \"/timer/continue\"),\n\n /** Stop timer and save sessions */\n stop: (): Promise<StopTimerResponse> => request(\"POST\", \"/timer/stop\"),\n\n /** Cancel timer without saving sessions */\n cancel: (): Promise<TimerState> => request(\"DELETE\", \"/timer\"),\n },\n\n // ============ Tasks ============\n tasks: {\n /** List all tasks */\n list: (params: TaskListParams = {}): Promise<PaginatedResponse<Task>> =>\n request(\"GET\", `/tasks${buildQueryString(params)}`),\n\n /** Get a single task by ID */\n get: (id: string): Promise<Task> => request(\"GET\", `/tasks/${id}`),\n\n /** Create a new task */\n create: (data: CreateTaskRequest): Promise<Task> =>\n request(\"POST\", \"/tasks\", data),\n\n /** Update an existing task */\n update: (id: string, data: UpdateTaskRequest): Promise<Task> =>\n request(\"PATCH\", `/tasks/${id}`, data),\n\n /** Delete a task */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/tasks/${id}`),\n\n /** Get tasks organized by section (today, sooner, later) */\n sections: (\n params: TaskSectionsParams = {}\n ): Promise<TaskSectionsResponse> =>\n request(\"GET\", `/tasks/sections${buildQueryString(params)}`),\n\n /** List subtasks for a task */\n subtasks: (\n id: string,\n params: SubtaskListParams = {}\n ): Promise<PaginatedResponse<Task>> =>\n request(\"GET\", `/tasks/${id}/subtasks${buildQueryString(params)}`),\n\n /** Create a subtask under a parent task */\n createSubtask: (\n parentId: string,\n data: Omit<CreateTaskRequest, \"parentId\" | \"section\">\n ): Promise<Task> => request(\"POST\", \"/tasks\", { ...data, parentId }),\n },\n\n // ============ Projects ============\n projects: {\n /** List all projects */\n list: (\n params: ProjectListParams = {}\n ): Promise<PaginatedResponse<Project>> =>\n request(\"GET\", `/projects${buildQueryString(params)}`),\n\n /** Get a single project by ID */\n get: (id: string): Promise<Project> => request(\"GET\", `/projects/${id}`),\n\n /** Create a new project */\n create: (data: CreateProjectRequest): Promise<Project> =>\n request(\"POST\", \"/projects\", data),\n\n /** Update an existing project */\n update: (id: string, data: UpdateProjectRequest): Promise<Project> =>\n request(\"PATCH\", `/projects/${id}`, data),\n\n /** Delete a project */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/projects/${id}`),\n },\n\n // ============ Notes ============\n notes: {\n /** List all notes */\n list: (params: NoteListParams = {}): Promise<PaginatedResponse<Note>> =>\n request(\"GET\", `/notes${buildQueryString(params)}`),\n\n /** Get a single note by ID */\n get: (id: string): Promise<Note> => request(\"GET\", `/notes/${id}`),\n\n /** Create a new note */\n create: (data: CreateNoteRequest): Promise<Note> =>\n request(\"POST\", \"/notes\", data),\n\n /** Update an existing note */\n update: (id: string, data: UpdateNoteRequest): Promise<Note> =>\n request(\"PATCH\", `/notes/${id}`, data),\n\n /** Delete a note */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/notes/${id}`),\n },\n\n // ============ Sessions ============\n sessions: {\n /** List all sessions */\n list: (\n params: SessionListParams = {}\n ): Promise<PaginatedResponse<SessionWithActivities>> =>\n request(\"GET\", `/sessions${buildQueryString(params)}`),\n\n /** Get a single session by ID */\n get: (id: string): Promise<SessionWithActivities> =>\n request(\"GET\", `/sessions/${id}`),\n\n /** Create a new session */\n create: (data: CreateSessionRequest): Promise<Session> =>\n request(\"POST\", \"/sessions\", data),\n\n /** Update an existing session */\n update: (id: string, data: UpdateSessionRequest): Promise<Session> =>\n request(\"PATCH\", `/sessions/${id}`, data),\n\n /** Delete a session */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/sessions/${id}`),\n\n // Activities\n activities: {\n /** List activities for a session */\n list: (sessionId: string): Promise<{ data: SessionActivity[] }> =>\n request(\"GET\", `/sessions/${sessionId}/activities`),\n\n /** Create a new activitie */\n create: (\n sessionId: string,\n data: CreateActivityRequest\n ): Promise<SessionActivity> =>\n request(\"POST\", `/sessions/${sessionId}/activities`, data),\n\n /** Update an activitie */\n update: (\n sessionId: string,\n activityId: string,\n data: UpdateActivityRequest\n ): Promise<SessionActivity> =>\n request(\n \"PATCH\",\n `/sessions/${sessionId}/activities/${activityId}`,\n data\n ),\n\n /** Delete an activitie */\n delete: (\n sessionId: string,\n activityId: string\n ): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/sessions/${sessionId}/activities/${activityId}`),\n },\n },\n\n // ============ Webhooks ============\n webhooks: {\n /** List all webhooks */\n list: (\n params: WebhookListParams = {}\n ): Promise<PaginatedResponse<Webhook>> =>\n request(\"GET\", `/webhooks${buildQueryString(params)}`),\n\n /** Get a single webhook by ID */\n get: (id: string): Promise<Webhook> => request(\"GET\", `/webhooks/${id}`),\n\n /** Create a new webhook */\n create: (data: CreateWebhookRequest): Promise<WebhookWithSecret> =>\n request(\"POST\", \"/webhooks\", data),\n\n /** Update an existing webhook */\n update: (id: string, data: UpdateWebhookRequest): Promise<Webhook> =>\n request(\"PATCH\", `/webhooks/${id}`, data),\n\n /** Delete a webhook */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/webhooks/${id}`),\n\n /** Rotate webhook secret */\n rotateSecret: (id: string): Promise<{ secret: string }> =>\n request(\"POST\", `/webhooks/${id}/rotate-secret`),\n\n /** List deliveries for a webhook */\n deliveries: (\n id: string,\n params: PaginationParams = {}\n ): Promise<PaginatedResponse<WebhookDelivery>> =>\n request(\"GET\", `/webhooks/${id}/deliveries${buildQueryString(params)}`),\n },\n }\n}\n\nexport type LocuClient = ReturnType<typeof createLocuClient>\n","import { createHmac, timingSafeEqual } from \"crypto\"\nimport type { WebhookPayload } from \"./types\"\n\nexport type WebhookSignatureResult =\n | { valid: true }\n | { valid: false; error: string }\n\nexport type ParsedWebhookSignature = {\n timestamp: number\n signature: string\n}\n\nexport type VerifyWebhookOptions = {\n /** Maximum age of signature in seconds (default: 300 = 5 minutes) */\n maxAge?: number\n}\n\n/**\n * Parse a webhook signature header into its components.\n *\n * The signature header format is: `t=<timestamp>,v1=<hex_signature>`\n *\n * @param signatureHeader - The X-Webhook-Signature header value\n * @returns Parsed timestamp and signature, or null if invalid format\n *\n * @example\n * ```typescript\n * const parsed = parseWebhookSignature(request.headers['x-webhook-signature'])\n * if (parsed) {\n * console.log('Timestamp:', parsed.timestamp)\n * console.log('Signature:', parsed.signature)\n * }\n * ```\n */\nexport const parseWebhookSignature = (\n signatureHeader: string\n): ParsedWebhookSignature | null => {\n const parts = signatureHeader.split(\",\")\n\n let timestamp: number | null = null\n let signature: string | null = null\n\n for (const part of parts) {\n const eqIndex = part.indexOf(\"=\")\n if (eqIndex === -1) continue\n const key = part.slice(0, eqIndex)\n const value = part.slice(eqIndex + 1)\n if (key === \"t\") {\n timestamp = parseInt(value, 10)\n } else if (key === \"v1\") {\n signature = value\n }\n }\n\n if (timestamp === null || signature === null || isNaN(timestamp)) {\n return null\n }\n\n return { timestamp, signature }\n}\n\n/**\n * Verify a webhook signature using HMAC-SHA256.\n *\n * This function verifies that a webhook payload was signed by Locu using your webhook secret.\n * It also checks that the signature timestamp is not too old to prevent replay attacks.\n *\n * @param secret - Your webhook secret (starts with `whsec_`)\n * @param signatureHeader - The X-Webhook-Signature header value\n * @param body - The raw request body as a string\n * @param options - Optional verification settings\n * @returns Object with `valid: true` if valid, or `valid: false` with an error message\n *\n * @example\n * ```typescript\n * import { verifyWebhookSignature } from '@locu/api-client'\n *\n * app.post('/webhooks/locu', (req, res) => {\n * const result = verifyWebhookSignature(\n * process.env.LOCU_WEBHOOK_SECRET,\n * req.headers['x-webhook-signature'],\n * req.body, // raw body string\n * { maxAge: 300 } // 5 minutes\n * )\n *\n * if (!result.valid) {\n * return res.status(401).json({ error: result.error })\n * }\n *\n * // Process the webhook\n * const payload = JSON.parse(req.body)\n * console.log('Received event:', payload.event)\n * })\n * ```\n */\nexport const verifyWebhookSignature = (\n secret: string,\n signatureHeader: string,\n body: string,\n options?: VerifyWebhookOptions\n): WebhookSignatureResult => {\n const parsed = parseWebhookSignature(signatureHeader)\n\n if (!parsed) {\n return { valid: false, error: \"Invalid signature format\" }\n }\n\n const { timestamp, signature } = parsed\n\n // Check timestamp age if maxAge is specified\n if (options?.maxAge !== undefined) {\n const now = Math.floor(Date.now() / 1000)\n const age = now - timestamp\n\n if (age > options.maxAge) {\n return { valid: false, error: \"Signature timestamp too old\" }\n }\n\n if (age < -60) {\n // Allow 1 minute clock skew into the future\n return { valid: false, error: \"Signature timestamp in the future\" }\n }\n }\n\n // Compute expected signature\n const signaturePayload = `${timestamp}.${body}`\n const expectedSignature = createHmac(\"sha256\", secret)\n .update(signaturePayload)\n .digest(\"hex\")\n\n // Use timing-safe comparison to prevent timing attacks\n const signatureBuffer = Buffer.from(signature, \"hex\")\n const expectedBuffer = Buffer.from(expectedSignature, \"hex\")\n\n if (signatureBuffer.length !== expectedBuffer.length) {\n return { valid: false, error: \"Invalid signature\" }\n }\n\n const isValid = timingSafeEqual(signatureBuffer, expectedBuffer)\n\n if (!isValid) {\n return { valid: false, error: \"Invalid signature\" }\n }\n\n return { valid: true }\n}\n\n/**\n * Parse a webhook payload from a JSON string.\n *\n * @param body - The raw request body as a JSON string\n * @returns The parsed webhook payload\n *\n * @example\n * ```typescript\n * import { parseWebhookPayload, TaskWebhookPayload } from '@locu/api-client'\n *\n * const payload = parseWebhookPayload<TaskWebhookPayload>(req.body)\n * console.log('Event:', payload.event) // e.g., \"task.created\"\n * console.log('Task name:', payload.data.name)\n * ```\n */\nexport const parseWebhookPayload = <T = unknown>(\n body: string\n): WebhookPayload<T> => {\n return JSON.parse(body) as WebhookPayload<T>\n}\n\n/**\n * Generate a webhook signature for testing purposes.\n *\n * This is useful for testing your webhook handlers locally.\n *\n * @param secret - Your webhook secret\n * @param timestamp - Unix timestamp in seconds\n * @param body - The request body as a string\n * @returns The signature header value in format `t=<timestamp>,v1=<signature>`\n *\n * @example\n * ```typescript\n * import { generateWebhookSignature } from '@locu/api-client'\n *\n * const body = JSON.stringify({ event: 'task.created', timestamp: '...', data: {...} })\n * const signature = generateWebhookSignature('whsec_...', Math.floor(Date.now() / 1000), body)\n * // Use signature for testing your webhook handler\n * ```\n */\nexport const generateWebhookSignature = (\n secret: string,\n timestamp: number,\n body: string\n): string => {\n const signaturePayload = `${timestamp}.${body}`\n const signature = createHmac(\"sha256\", secret)\n .update(signaturePayload)\n .digest(\"hex\")\n return `t=${timestamp},v1=${signature}`\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/webhook.ts"],"sourcesContent":["import type {\n ActivityListParams,\n ApiError,\n CreateActivityRequest,\n CreateNoteRequest,\n CreateProjectRequest,\n CreateSessionRequest,\n CreateTaskRequest,\n CreateWebhookRequest,\n MeResponse,\n Note,\n NoteListParams,\n PaginatedResponse,\n PaginationParams,\n Project,\n ProjectListParams,\n Session,\n SessionActivity,\n SessionListParams,\n SessionWithActivities,\n StartTimerRequest,\n StopTimerResponse,\n SubtaskListParams,\n Task,\n TaskListParams,\n TaskSectionsParams,\n TaskSectionsResponse,\n TimerState,\n UpdateActivityRequest,\n UpdateNoteRequest,\n UpdateProjectRequest,\n UpdateSessionRequest,\n UpdateTaskRequest,\n UpdateWebhookRequest,\n Webhook,\n WebhookDelivery,\n WebhookListParams,\n WebhookWithSecret,\n} from \"./types\"\n\nexport type LocuClientConfig = {\n /** API base URL (defaults to https://api.locu.app/api/v1) */\n baseUrl?: string\n /** Personal Access Token for authentication */\n token: string\n /** Custom fetch implementation (defaults to global fetch) */\n fetch?: typeof fetch\n}\n\nexport class LocuApiError extends Error {\n status: number\n code?: string\n\n constructor(message: string, status: number, code?: string) {\n super(message)\n this.name = \"LocuApiError\"\n this.status = status\n this.code = code\n }\n}\n\nconst buildQueryString = (params: Record<string, unknown>): string => {\n const searchParams = new URLSearchParams()\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n searchParams.set(key, String(value))\n }\n }\n const qs = searchParams.toString()\n return qs ? `?${qs}` : \"\"\n}\n\nexport const createLocuClient = (config: LocuClientConfig) => {\n const baseUrl = config.baseUrl || \"https://api.locu.app/api/v1\"\n const fetchFn = config.fetch || fetch\n\n const request = async <T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> => {\n const url = `${baseUrl}${path}`\n const headers: Record<string, string> = {\n Authorization: `Bearer ${config.token}`,\n \"Content-Type\": \"application/json\",\n }\n\n const response = await fetchFn(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n })\n\n if (!response.ok) {\n let errorData: ApiError | null = null\n try {\n errorData = await response.json()\n } catch {\n // Ignore JSON parse errors\n }\n throw new LocuApiError(\n errorData?.message || `Request failed with status ${response.status}`,\n response.status,\n errorData?.code\n )\n }\n\n // Handle 204 No Content\n if (response.status === 204) {\n return undefined as T\n }\n\n return response.json()\n }\n\n return {\n // ============ Me ============\n me: {\n /** Get current me */\n get: (): Promise<MeResponse> => request(\"GET\", \"/me\"),\n },\n\n // ============ Timer ============\n timer: {\n /** Get current timer */\n get: (): Promise<TimerState> => request(\"GET\", \"/timer\"),\n\n /** Start a new timer */\n start: (data?: StartTimerRequest): Promise<TimerState> =>\n request(\"POST\", \"/timer/start\", data),\n\n /** Pause the running timer */\n pause: (): Promise<TimerState> => request(\"POST\", \"/timer/pause\"),\n\n /** Resume a paused timer */\n continue: (): Promise<TimerState> => request(\"POST\", \"/timer/continue\"),\n\n /** Stop timer and save sessions */\n stop: (): Promise<StopTimerResponse> => request(\"POST\", \"/timer/stop\"),\n\n /** Cancel timer without saving sessions */\n cancel: (): Promise<TimerState> => request(\"DELETE\", \"/timer\"),\n },\n\n // ============ Tasks ============\n tasks: {\n /** List all tasks */\n list: (params: TaskListParams = {}): Promise<PaginatedResponse<Task>> =>\n request(\"GET\", `/tasks${buildQueryString(params)}`),\n\n /** Get a single task by ID */\n get: (id: string): Promise<Task> => request(\"GET\", `/tasks/${id}`),\n\n /** Create a new task */\n create: (data: CreateTaskRequest): Promise<Task> =>\n request(\"POST\", \"/tasks\", data),\n\n /** Update an existing task */\n update: (id: string, data: UpdateTaskRequest): Promise<Task> =>\n request(\"PATCH\", `/tasks/${id}`, data),\n\n /** Delete a task */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/tasks/${id}`),\n\n /** Get tasks organized by section (today, sooner, later) */\n sections: (\n params: TaskSectionsParams = {}\n ): Promise<TaskSectionsResponse> =>\n request(\"GET\", `/tasks/sections${buildQueryString(params)}`),\n\n /** List subtasks for a task */\n subtasks: (\n id: string,\n params: SubtaskListParams = {}\n ): Promise<PaginatedResponse<Task>> =>\n request(\"GET\", `/tasks/${id}/subtasks${buildQueryString(params)}`),\n\n /** Create a subtask under a parent task */\n createSubtask: (\n parentId: string,\n data: Omit<CreateTaskRequest, \"parentId\" | \"section\">\n ): Promise<Task> => request(\"POST\", \"/tasks\", { ...data, parentId }),\n },\n\n // ============ Projects ============\n projects: {\n /** List all projects */\n list: (\n params: ProjectListParams = {}\n ): Promise<PaginatedResponse<Project>> =>\n request(\"GET\", `/projects${buildQueryString(params)}`),\n\n /** Get a single project by ID */\n get: (id: string): Promise<Project> => request(\"GET\", `/projects/${id}`),\n\n /** Create a new project */\n create: (data: CreateProjectRequest): Promise<Project> =>\n request(\"POST\", \"/projects\", data),\n\n /** Update an existing project */\n update: (id: string, data: UpdateProjectRequest): Promise<Project> =>\n request(\"PATCH\", `/projects/${id}`, data),\n\n /** Delete a project */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/projects/${id}`),\n },\n\n // ============ Notes ============\n notes: {\n /** List all notes */\n list: (params: NoteListParams = {}): Promise<PaginatedResponse<Note>> =>\n request(\"GET\", `/notes${buildQueryString(params)}`),\n\n /** Get a single note by ID */\n get: (id: string): Promise<Note> => request(\"GET\", `/notes/${id}`),\n\n /** Create a new note */\n create: (data: CreateNoteRequest): Promise<Note> =>\n request(\"POST\", \"/notes\", data),\n\n /** Update an existing note */\n update: (id: string, data: UpdateNoteRequest): Promise<Note> =>\n request(\"PATCH\", `/notes/${id}`, data),\n\n /** Delete a note */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/notes/${id}`),\n },\n\n // ============ Sessions ============\n sessions: {\n /** List all sessions */\n list: (\n params: SessionListParams = {}\n ): Promise<PaginatedResponse<SessionWithActivities>> =>\n request(\"GET\", `/sessions${buildQueryString(params)}`),\n\n /** Get a single session by ID */\n get: (id: string): Promise<SessionWithActivities> =>\n request(\"GET\", `/sessions/${id}`),\n\n /** Create a new session */\n create: (data: CreateSessionRequest): Promise<Session> =>\n request(\"POST\", \"/sessions\", data),\n\n /** Update an existing session */\n update: (id: string, data: UpdateSessionRequest): Promise<Session> =>\n request(\"PATCH\", `/sessions/${id}`, data),\n\n /** Delete a session */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/sessions/${id}`),\n\n /** List all activities with optional filters */\n listActivities: (\n params: ActivityListParams = {}\n ): Promise<PaginatedResponse<SessionActivity>> =>\n request(\"GET\", `/sessions/activities${buildQueryString(params)}`),\n\n // Activities\n activities: {\n /** List activities for a session */\n list: (sessionId: string): Promise<{ data: SessionActivity[] }> =>\n request(\"GET\", `/sessions/${sessionId}/activities`),\n\n /** Create a new activitie */\n create: (\n sessionId: string,\n data: CreateActivityRequest\n ): Promise<SessionActivity> =>\n request(\"POST\", `/sessions/${sessionId}/activities`, data),\n\n /** Update an activitie */\n update: (\n sessionId: string,\n activityId: string,\n data: UpdateActivityRequest\n ): Promise<SessionActivity> =>\n request(\n \"PATCH\",\n `/sessions/${sessionId}/activities/${activityId}`,\n data\n ),\n\n /** Delete an activitie */\n delete: (\n sessionId: string,\n activityId: string\n ): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/sessions/${sessionId}/activities/${activityId}`),\n },\n },\n\n // ============ Webhooks ============\n webhooks: {\n /** List all webhooks */\n list: (\n params: WebhookListParams = {}\n ): Promise<PaginatedResponse<Webhook>> =>\n request(\"GET\", `/webhooks${buildQueryString(params)}`),\n\n /** Get a single webhook by ID */\n get: (id: string): Promise<Webhook> => request(\"GET\", `/webhooks/${id}`),\n\n /** Create a new webhook */\n create: (data: CreateWebhookRequest): Promise<WebhookWithSecret> =>\n request(\"POST\", \"/webhooks\", data),\n\n /** Update an existing webhook */\n update: (id: string, data: UpdateWebhookRequest): Promise<Webhook> =>\n request(\"PATCH\", `/webhooks/${id}`, data),\n\n /** Delete a webhook */\n delete: (id: string): Promise<{ success: boolean }> =>\n request(\"DELETE\", `/webhooks/${id}`),\n\n /** Rotate webhook secret */\n rotateSecret: (id: string): Promise<{ secret: string }> =>\n request(\"POST\", `/webhooks/${id}/rotate-secret`),\n\n /** List deliveries for a webhook */\n deliveries: (\n id: string,\n params: PaginationParams = {}\n ): Promise<PaginatedResponse<WebhookDelivery>> =>\n request(\"GET\", `/webhooks/${id}/deliveries${buildQueryString(params)}`),\n },\n }\n}\n\nexport type LocuClient = ReturnType<typeof createLocuClient>\n","import { createHmac, timingSafeEqual } from \"crypto\"\nimport type { WebhookPayload } from \"./types\"\n\nexport type WebhookSignatureResult =\n | { valid: true }\n | { valid: false; error: string }\n\nexport type ParsedWebhookSignature = {\n timestamp: number\n signature: string\n}\n\nexport type VerifyWebhookOptions = {\n /** Maximum age of signature in seconds (default: 300 = 5 minutes) */\n maxAge?: number\n}\n\n/**\n * Parse a webhook signature header into its components.\n *\n * The signature header format is: `t=<timestamp>,v1=<hex_signature>`\n *\n * @param signatureHeader - The X-Webhook-Signature header value\n * @returns Parsed timestamp and signature, or null if invalid format\n *\n * @example\n * ```typescript\n * const parsed = parseWebhookSignature(request.headers['x-webhook-signature'])\n * if (parsed) {\n * console.log('Timestamp:', parsed.timestamp)\n * console.log('Signature:', parsed.signature)\n * }\n * ```\n */\nexport const parseWebhookSignature = (\n signatureHeader: string\n): ParsedWebhookSignature | null => {\n const parts = signatureHeader.split(\",\")\n\n let timestamp: number | null = null\n let signature: string | null = null\n\n for (const part of parts) {\n const eqIndex = part.indexOf(\"=\")\n if (eqIndex === -1) continue\n const key = part.slice(0, eqIndex)\n const value = part.slice(eqIndex + 1)\n if (key === \"t\") {\n timestamp = parseInt(value, 10)\n } else if (key === \"v1\") {\n signature = value\n }\n }\n\n if (timestamp === null || signature === null || isNaN(timestamp)) {\n return null\n }\n\n return { timestamp, signature }\n}\n\n/**\n * Verify a webhook signature using HMAC-SHA256.\n *\n * This function verifies that a webhook payload was signed by Locu using your webhook secret.\n * It also checks that the signature timestamp is not too old to prevent replay attacks.\n *\n * @param secret - Your webhook secret (starts with `whsec_`)\n * @param signatureHeader - The X-Webhook-Signature header value\n * @param body - The raw request body as a string\n * @param options - Optional verification settings\n * @returns Object with `valid: true` if valid, or `valid: false` with an error message\n *\n * @example\n * ```typescript\n * import { verifyWebhookSignature } from '@locu/api-client'\n *\n * app.post('/webhooks/locu', (req, res) => {\n * const result = verifyWebhookSignature(\n * process.env.LOCU_WEBHOOK_SECRET,\n * req.headers['x-webhook-signature'],\n * req.body, // raw body string\n * { maxAge: 300 } // 5 minutes\n * )\n *\n * if (!result.valid) {\n * return res.status(401).json({ error: result.error })\n * }\n *\n * // Process the webhook\n * const payload = JSON.parse(req.body)\n * console.log('Received event:', payload.event)\n * })\n * ```\n */\nexport const verifyWebhookSignature = (\n secret: string,\n signatureHeader: string,\n body: string,\n options?: VerifyWebhookOptions\n): WebhookSignatureResult => {\n const parsed = parseWebhookSignature(signatureHeader)\n\n if (!parsed) {\n return { valid: false, error: \"Invalid signature format\" }\n }\n\n const { timestamp, signature } = parsed\n\n // Check timestamp age if maxAge is specified\n if (options?.maxAge !== undefined) {\n const now = Math.floor(Date.now() / 1000)\n const age = now - timestamp\n\n if (age > options.maxAge) {\n return { valid: false, error: \"Signature timestamp too old\" }\n }\n\n if (age < -60) {\n // Allow 1 minute clock skew into the future\n return { valid: false, error: \"Signature timestamp in the future\" }\n }\n }\n\n // Compute expected signature\n const signaturePayload = `${timestamp}.${body}`\n const expectedSignature = createHmac(\"sha256\", secret)\n .update(signaturePayload)\n .digest(\"hex\")\n\n // Use timing-safe comparison to prevent timing attacks\n const signatureBuffer = Buffer.from(signature, \"hex\")\n const expectedBuffer = Buffer.from(expectedSignature, \"hex\")\n\n if (signatureBuffer.length !== expectedBuffer.length) {\n return { valid: false, error: \"Invalid signature\" }\n }\n\n const isValid = timingSafeEqual(signatureBuffer, expectedBuffer)\n\n if (!isValid) {\n return { valid: false, error: \"Invalid signature\" }\n }\n\n return { valid: true }\n}\n\n/**\n * Parse a webhook payload from a JSON string.\n *\n * @param body - The raw request body as a JSON string\n * @returns The parsed webhook payload\n *\n * @example\n * ```typescript\n * import { parseWebhookPayload, TaskWebhookPayload } from '@locu/api-client'\n *\n * const payload = parseWebhookPayload<TaskWebhookPayload>(req.body)\n * console.log('Event:', payload.event) // e.g., \"task.created\"\n * console.log('Task name:', payload.data.name)\n * ```\n */\nexport const parseWebhookPayload = <T = unknown>(\n body: string\n): WebhookPayload<T> => {\n return JSON.parse(body) as WebhookPayload<T>\n}\n\n/**\n * Generate a webhook signature for testing purposes.\n *\n * This is useful for testing your webhook handlers locally.\n *\n * @param secret - Your webhook secret\n * @param timestamp - Unix timestamp in seconds\n * @param body - The request body as a string\n * @returns The signature header value in format `t=<timestamp>,v1=<signature>`\n *\n * @example\n * ```typescript\n * import { generateWebhookSignature } from '@locu/api-client'\n *\n * const body = JSON.stringify({ event: 'task.created', timestamp: '...', data: {...} })\n * const signature = generateWebhookSignature('whsec_...', Math.floor(Date.now() / 1000), body)\n * // Use signature for testing your webhook handler\n * ```\n */\nexport const generateWebhookSignature = (\n secret: string,\n timestamp: number,\n body: string\n): string => {\n const signaturePayload = `${timestamp}.${body}`\n const signature = createHmac(\"sha256\", secret)\n .update(signaturePayload)\n .digest(\"hex\")\n return `t=${timestamp},v1=${signature}`\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loculabs/api-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "TypeScript client for the Locu REST API",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"@types/node": "^20.10.0",
|
|
67
67
|
"openapi-typescript": "^7.10.1",
|
|
68
68
|
"prettier": "^3.7.4",
|
|
69
|
-
"semantic-release": "
|
|
69
|
+
"semantic-release": "25.0.2",
|
|
70
70
|
"tsup": "^8.5.1",
|
|
71
71
|
"tsx": "^4.21.0",
|
|
72
72
|
"typescript": "^5.9.3",
|