@loculabs/api-client 1.0.0 → 1.1.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 CHANGED
@@ -1,3 +1,10 @@
1
+ # [1.1.0](https://github.com/loculabs/api-client/compare/v1.0.0...v1.1.0) (2026-01-21)
2
+
3
+
4
+ ### Features
5
+
6
+ * add /me endpoint and refactor client generator ([20dde23](https://github.com/loculabs/api-client/commit/20dde231a71e45f366f3ed1a2501dfe9c6432ce9))
7
+
1
8
  # 1.0.0 (2025-12-31)
2
9
 
3
10
 
package/dist/index.d.mts CHANGED
@@ -176,6 +176,17 @@ interface components {
176
176
  type: "jira";
177
177
  };
178
178
  Task: components["schemas"]["LocuTask"] | components["schemas"]["LinearTask"] | components["schemas"]["JiraTask"];
179
+ MeResponse: {
180
+ /** @description Email of the authenticated user */
181
+ email: string;
182
+ /** @description Name of the workspace */
183
+ workspaceName: string;
184
+ };
185
+ ErrorResponse: {
186
+ error: string;
187
+ message: string;
188
+ code?: string;
189
+ };
179
190
  Note: {
180
191
  id: string;
181
192
  text: string;
@@ -258,11 +269,6 @@ interface components {
258
269
  nextCursor: string | null;
259
270
  hasMore: boolean;
260
271
  };
261
- ErrorResponse: {
262
- error: string;
263
- message: string;
264
- code?: string;
265
- };
266
272
  CreateNoteRequest: {
267
273
  /**
268
274
  * Format: uuid
@@ -791,6 +797,7 @@ type WebhookDeliveryListResponse = components["schemas"]["WebhookDeliveryListRes
791
797
  type RotateSecretResponse = components["schemas"]["RotateSecretResponse"];
792
798
  type DeleteWebhookResponse = components["schemas"]["DeleteWebhookResponse"];
793
799
  type ErrorResponse = components["schemas"]["ErrorResponse"];
800
+ type MeResponse = components["schemas"]["MeResponse"];
794
801
  type ApiError = ErrorResponse;
795
802
  type WebhookPayload<T = unknown> = {
796
803
  /** Event type, e.g. "task.created", "project.updated" */
@@ -857,6 +864,22 @@ declare class LocuApiError extends Error {
857
864
  constructor(message: string, status: number, code?: string);
858
865
  }
859
866
  declare const createLocuClient: (config: LocuClientConfig) => {
867
+ me: {
868
+ /** Get current me */
869
+ get: () => Promise<MeResponse>;
870
+ };
871
+ timer: {
872
+ /** Get current timer */
873
+ get: () => Promise<TimerState>;
874
+ /** Start a new timer */
875
+ start: (data?: StartTimerRequest) => Promise<TimerState>;
876
+ /** Pause the running timer */
877
+ pause: () => Promise<TimerState>;
878
+ /** Resume a paused timer */
879
+ continue: () => Promise<TimerState>;
880
+ /** Stop timer and save sessions */
881
+ stop: () => Promise<StopTimerResponse>;
882
+ };
860
883
  tasks: {
861
884
  /** List all tasks */
862
885
  list: (params?: TaskListParams) => Promise<PaginatedResponse<Task>>;
@@ -953,18 +976,6 @@ declare const createLocuClient: (config: LocuClientConfig) => {
953
976
  /** List deliveries for a webhook */
954
977
  deliveries: (id: string, params?: PaginationParams) => Promise<PaginatedResponse<WebhookDelivery>>;
955
978
  };
956
- timer: {
957
- /** Get current timer state */
958
- get: () => Promise<TimerState>;
959
- /** Start a new timer */
960
- start: (data?: StartTimerRequest) => Promise<TimerState>;
961
- /** Pause the running timer */
962
- pause: () => Promise<TimerState>;
963
- /** Resume a paused timer */
964
- continue: () => Promise<TimerState>;
965
- /** Stop timer and save sessions */
966
- stop: () => Promise<StopTimerResponse>;
967
- };
968
979
  };
969
980
  type LocuClient = ReturnType<typeof createLocuClient>;
970
981
 
@@ -1072,4 +1083,4 @@ declare const parseWebhookPayload: <T = unknown>(body: string) => WebhookPayload
1072
1083
  */
1073
1084
  declare const generateWebhookSignature: (secret: string, timestamp: number, body: string) => string;
1074
1085
 
1075
- export { type ActivityListResponse, type ApiError, type CreateActivityRequest, type CreateNoteRequest, type CreateProjectRequest, type CreateSessionRequest, type CreateTaskRequest, type CreateWebhookRequest, type DeleteActivityResponse, type DeleteNoteResponse, type DeleteProjectResponse, type DeleteSessionResponse, type DeleteTaskResponse, type DeleteWebhookResponse, type ErrorResponse, type JiraTask, type LinearTask, LocuApiError, type LocuClient, type LocuClientConfig, type LocuTask, type Note, type NoteListParams, type NoteListResponse, type PaginatedResponse, type PaginationParams, type ParsedWebhookSignature, type Project, type ProjectDescription, type ProjectListParams, type ProjectListResponse, type RotateSecretResponse, type Session, type SessionActivity, type SessionListParams, type SessionListResponse, type SessionWithActivities, type StartTimerRequest, type StopTimerResponse, type StopTimerSession, type SubtaskListParams, type Task, type TaskBySection, type TaskDescription, type TaskListParams, type TaskListResponse, type TaskSectionsParams, type TaskSectionsResponse, type TimerState, type UpdateActivityRequest, type UpdateNoteRequest, type UpdateProjectRequest, type UpdateSessionRequest, type UpdateTaskRequest, type UpdateWebhookRequest, type VerifyWebhookOptions, type Webhook, type WebhookDelivery, type WebhookDeliveryListResponse, type WebhookListParams, type WebhookListResponse, type WebhookPayload, type WebhookSignatureResult, type WebhookWithSecret, createLocuClient, generateWebhookSignature, parseWebhookPayload, parseWebhookSignature, verifyWebhookSignature };
1086
+ export { type ActivityListResponse, type ApiError, type CreateActivityRequest, type CreateNoteRequest, type CreateProjectRequest, type CreateSessionRequest, type CreateTaskRequest, type CreateWebhookRequest, type DeleteActivityResponse, type DeleteNoteResponse, type DeleteProjectResponse, type DeleteSessionResponse, type DeleteTaskResponse, type DeleteWebhookResponse, type ErrorResponse, type JiraTask, type LinearTask, LocuApiError, type LocuClient, type LocuClientConfig, type LocuTask, type MeResponse, type Note, type NoteListParams, type NoteListResponse, type PaginatedResponse, type PaginationParams, type ParsedWebhookSignature, type Project, type ProjectDescription, type ProjectListParams, type ProjectListResponse, type RotateSecretResponse, type Session, type SessionActivity, type SessionListParams, type SessionListResponse, type SessionWithActivities, type StartTimerRequest, type StopTimerResponse, type StopTimerSession, type SubtaskListParams, type Task, type TaskBySection, type TaskDescription, type TaskListParams, type TaskListResponse, type TaskSectionsParams, type TaskSectionsResponse, type TimerState, type UpdateActivityRequest, type UpdateNoteRequest, type UpdateProjectRequest, type UpdateSessionRequest, type UpdateTaskRequest, type UpdateWebhookRequest, type VerifyWebhookOptions, type Webhook, type WebhookDelivery, type WebhookDeliveryListResponse, type WebhookListParams, type WebhookListResponse, type WebhookPayload, type WebhookSignatureResult, type WebhookWithSecret, createLocuClient, generateWebhookSignature, parseWebhookPayload, parseWebhookSignature, verifyWebhookSignature };
package/dist/index.d.ts CHANGED
@@ -176,6 +176,17 @@ interface components {
176
176
  type: "jira";
177
177
  };
178
178
  Task: components["schemas"]["LocuTask"] | components["schemas"]["LinearTask"] | components["schemas"]["JiraTask"];
179
+ MeResponse: {
180
+ /** @description Email of the authenticated user */
181
+ email: string;
182
+ /** @description Name of the workspace */
183
+ workspaceName: string;
184
+ };
185
+ ErrorResponse: {
186
+ error: string;
187
+ message: string;
188
+ code?: string;
189
+ };
179
190
  Note: {
180
191
  id: string;
181
192
  text: string;
@@ -258,11 +269,6 @@ interface components {
258
269
  nextCursor: string | null;
259
270
  hasMore: boolean;
260
271
  };
261
- ErrorResponse: {
262
- error: string;
263
- message: string;
264
- code?: string;
265
- };
266
272
  CreateNoteRequest: {
267
273
  /**
268
274
  * Format: uuid
@@ -791,6 +797,7 @@ type WebhookDeliveryListResponse = components["schemas"]["WebhookDeliveryListRes
791
797
  type RotateSecretResponse = components["schemas"]["RotateSecretResponse"];
792
798
  type DeleteWebhookResponse = components["schemas"]["DeleteWebhookResponse"];
793
799
  type ErrorResponse = components["schemas"]["ErrorResponse"];
800
+ type MeResponse = components["schemas"]["MeResponse"];
794
801
  type ApiError = ErrorResponse;
795
802
  type WebhookPayload<T = unknown> = {
796
803
  /** Event type, e.g. "task.created", "project.updated" */
@@ -857,6 +864,22 @@ declare class LocuApiError extends Error {
857
864
  constructor(message: string, status: number, code?: string);
858
865
  }
859
866
  declare const createLocuClient: (config: LocuClientConfig) => {
867
+ me: {
868
+ /** Get current me */
869
+ get: () => Promise<MeResponse>;
870
+ };
871
+ timer: {
872
+ /** Get current timer */
873
+ get: () => Promise<TimerState>;
874
+ /** Start a new timer */
875
+ start: (data?: StartTimerRequest) => Promise<TimerState>;
876
+ /** Pause the running timer */
877
+ pause: () => Promise<TimerState>;
878
+ /** Resume a paused timer */
879
+ continue: () => Promise<TimerState>;
880
+ /** Stop timer and save sessions */
881
+ stop: () => Promise<StopTimerResponse>;
882
+ };
860
883
  tasks: {
861
884
  /** List all tasks */
862
885
  list: (params?: TaskListParams) => Promise<PaginatedResponse<Task>>;
@@ -953,18 +976,6 @@ declare const createLocuClient: (config: LocuClientConfig) => {
953
976
  /** List deliveries for a webhook */
954
977
  deliveries: (id: string, params?: PaginationParams) => Promise<PaginatedResponse<WebhookDelivery>>;
955
978
  };
956
- timer: {
957
- /** Get current timer state */
958
- get: () => Promise<TimerState>;
959
- /** Start a new timer */
960
- start: (data?: StartTimerRequest) => Promise<TimerState>;
961
- /** Pause the running timer */
962
- pause: () => Promise<TimerState>;
963
- /** Resume a paused timer */
964
- continue: () => Promise<TimerState>;
965
- /** Stop timer and save sessions */
966
- stop: () => Promise<StopTimerResponse>;
967
- };
968
979
  };
969
980
  type LocuClient = ReturnType<typeof createLocuClient>;
970
981
 
@@ -1072,4 +1083,4 @@ declare const parseWebhookPayload: <T = unknown>(body: string) => WebhookPayload
1072
1083
  */
1073
1084
  declare const generateWebhookSignature: (secret: string, timestamp: number, body: string) => string;
1074
1085
 
1075
- export { type ActivityListResponse, type ApiError, type CreateActivityRequest, type CreateNoteRequest, type CreateProjectRequest, type CreateSessionRequest, type CreateTaskRequest, type CreateWebhookRequest, type DeleteActivityResponse, type DeleteNoteResponse, type DeleteProjectResponse, type DeleteSessionResponse, type DeleteTaskResponse, type DeleteWebhookResponse, type ErrorResponse, type JiraTask, type LinearTask, LocuApiError, type LocuClient, type LocuClientConfig, type LocuTask, type Note, type NoteListParams, type NoteListResponse, type PaginatedResponse, type PaginationParams, type ParsedWebhookSignature, type Project, type ProjectDescription, type ProjectListParams, type ProjectListResponse, type RotateSecretResponse, type Session, type SessionActivity, type SessionListParams, type SessionListResponse, type SessionWithActivities, type StartTimerRequest, type StopTimerResponse, type StopTimerSession, type SubtaskListParams, type Task, type TaskBySection, type TaskDescription, type TaskListParams, type TaskListResponse, type TaskSectionsParams, type TaskSectionsResponse, type TimerState, type UpdateActivityRequest, type UpdateNoteRequest, type UpdateProjectRequest, type UpdateSessionRequest, type UpdateTaskRequest, type UpdateWebhookRequest, type VerifyWebhookOptions, type Webhook, type WebhookDelivery, type WebhookDeliveryListResponse, type WebhookListParams, type WebhookListResponse, type WebhookPayload, type WebhookSignatureResult, type WebhookWithSecret, createLocuClient, generateWebhookSignature, parseWebhookPayload, parseWebhookSignature, verifyWebhookSignature };
1086
+ export { type ActivityListResponse, type ApiError, type CreateActivityRequest, type CreateNoteRequest, type CreateProjectRequest, type CreateSessionRequest, type CreateTaskRequest, type CreateWebhookRequest, type DeleteActivityResponse, type DeleteNoteResponse, type DeleteProjectResponse, type DeleteSessionResponse, type DeleteTaskResponse, type DeleteWebhookResponse, type ErrorResponse, type JiraTask, type LinearTask, LocuApiError, type LocuClient, type LocuClientConfig, type LocuTask, type MeResponse, type Note, type NoteListParams, type NoteListResponse, type PaginatedResponse, type PaginationParams, type ParsedWebhookSignature, type Project, type ProjectDescription, type ProjectListParams, type ProjectListResponse, type RotateSecretResponse, type Session, type SessionActivity, type SessionListParams, type SessionListResponse, type SessionWithActivities, type StartTimerRequest, type StopTimerResponse, type StopTimerSession, type SubtaskListParams, type Task, type TaskBySection, type TaskDescription, type TaskListParams, type TaskListResponse, type TaskSectionsParams, type TaskSectionsResponse, type TimerState, type UpdateActivityRequest, type UpdateNoteRequest, type UpdateProjectRequest, type UpdateSessionRequest, type UpdateTaskRequest, type UpdateWebhookRequest, type VerifyWebhookOptions, type Webhook, type WebhookDelivery, type WebhookDeliveryListResponse, type WebhookListParams, type WebhookListResponse, type WebhookPayload, type WebhookSignatureResult, type WebhookWithSecret, createLocuClient, generateWebhookSignature, parseWebhookPayload, parseWebhookSignature, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -82,6 +82,24 @@ var createLocuClient = (config) => {
82
82
  return response.json();
83
83
  };
84
84
  return {
85
+ // ============ Me ============
86
+ me: {
87
+ /** Get current me */
88
+ get: () => request("GET", "/me")
89
+ },
90
+ // ============ Timer ============
91
+ timer: {
92
+ /** Get current timer */
93
+ get: () => request("GET", "/timer"),
94
+ /** Start a new timer */
95
+ start: (data) => request("POST", "/timer/start", data),
96
+ /** Pause the running timer */
97
+ pause: () => request("POST", "/timer/pause"),
98
+ /** Resume a paused timer */
99
+ continue: () => request("POST", "/timer/continue"),
100
+ /** Stop timer and save sessions */
101
+ stop: () => request("POST", "/timer/stop")
102
+ },
85
103
  // ============ Tasks ============
86
104
  tasks: {
87
105
  /** List all tasks */
@@ -171,19 +189,6 @@ var createLocuClient = (config) => {
171
189
  rotateSecret: (id) => request("POST", `/webhooks/${id}/rotate-secret`),
172
190
  /** List deliveries for a webhook */
173
191
  deliveries: (id, params = {}) => request("GET", `/webhooks/${id}/deliveries${buildQueryString(params)}`)
174
- },
175
- // ============ Timer ============
176
- timer: {
177
- /** Get current timer state */
178
- get: () => request("GET", "/timer"),
179
- /** Start a new timer */
180
- start: (data) => request("POST", "/timer/start", data),
181
- /** Pause the running timer */
182
- pause: () => request("POST", "/timer/pause"),
183
- /** Resume a paused timer */
184
- continue: () => request("POST", "/timer/continue"),
185
- /** Stop timer and save sessions */
186
- stop: () => request("POST", "/timer/stop")
187
192
  }
188
193
  };
189
194
  };
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\n// Types - re-export everything from types module\nexport type * from \"./types\"\n","import type {\n ApiError,\n CreateActivityRequest,\n CreateNoteRequest,\n CreateProjectRequest,\n CreateSessionRequest,\n CreateTaskRequest,\n CreateWebhookRequest,\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 // ============ 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> =>\n 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: (params: TaskSectionsParams = {}): 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> =>\n request(\"POST\", \"/tasks\", { ...data, parentId }),\n },\n\n // ============ Projects ============\n projects: {\n /** List all projects */\n list: (params: ProjectListParams = {}): Promise<PaginatedResponse<Project>> =>\n request(\"GET\", `/projects${buildQueryString(params)}`),\n\n /** Get a single project by ID */\n get: (id: string): Promise<Project> =>\n 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\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> =>\n 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\n // ============ Sessions ============\n sessions: {\n /** List all sessions */\n list: (params: SessionListParams = {}): 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\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: (params: WebhookListParams = {}): Promise<PaginatedResponse<Webhook>> =>\n request(\"GET\", `/webhooks${buildQueryString(params)}`),\n\n /** Get a single webhook by ID */\n get: (id: string): Promise<Webhook> =>\n 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 // ============ Timer ============\n timer: {\n /** Get current timer state */\n get: (): Promise<TimerState> =>\n 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> =>\n request(\"POST\", \"/timer/pause\"),\n\n /** Resume a paused timer */\n continue: (): Promise<TimerState> =>\n request(\"POST\", \"/timer/continue\"),\n\n /** Stop timer and save sessions */\n stop: (): Promise<StopTimerResponse> =>\n request(\"POST\", \"/timer/stop\"),\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\n// Types - re-export everything from types module\nexport type * from \"./types\"\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\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":[]}
package/dist/index.mjs CHANGED
@@ -51,6 +51,24 @@ var createLocuClient = (config) => {
51
51
  return response.json();
52
52
  };
53
53
  return {
54
+ // ============ Me ============
55
+ me: {
56
+ /** Get current me */
57
+ get: () => request("GET", "/me")
58
+ },
59
+ // ============ Timer ============
60
+ timer: {
61
+ /** Get current timer */
62
+ get: () => request("GET", "/timer"),
63
+ /** Start a new timer */
64
+ start: (data) => request("POST", "/timer/start", data),
65
+ /** Pause the running timer */
66
+ pause: () => request("POST", "/timer/pause"),
67
+ /** Resume a paused timer */
68
+ continue: () => request("POST", "/timer/continue"),
69
+ /** Stop timer and save sessions */
70
+ stop: () => request("POST", "/timer/stop")
71
+ },
54
72
  // ============ Tasks ============
55
73
  tasks: {
56
74
  /** List all tasks */
@@ -140,19 +158,6 @@ var createLocuClient = (config) => {
140
158
  rotateSecret: (id) => request("POST", `/webhooks/${id}/rotate-secret`),
141
159
  /** List deliveries for a webhook */
142
160
  deliveries: (id, params = {}) => request("GET", `/webhooks/${id}/deliveries${buildQueryString(params)}`)
143
- },
144
- // ============ Timer ============
145
- timer: {
146
- /** Get current timer state */
147
- get: () => request("GET", "/timer"),
148
- /** Start a new timer */
149
- start: (data) => request("POST", "/timer/start", data),
150
- /** Pause the running timer */
151
- pause: () => request("POST", "/timer/pause"),
152
- /** Resume a paused timer */
153
- continue: () => request("POST", "/timer/continue"),
154
- /** Stop timer and save sessions */
155
- stop: () => request("POST", "/timer/stop")
156
161
  }
157
162
  };
158
163
  };
@@ -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 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 // ============ 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> =>\n 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: (params: TaskSectionsParams = {}): 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> =>\n request(\"POST\", \"/tasks\", { ...data, parentId }),\n },\n\n // ============ Projects ============\n projects: {\n /** List all projects */\n list: (params: ProjectListParams = {}): Promise<PaginatedResponse<Project>> =>\n request(\"GET\", `/projects${buildQueryString(params)}`),\n\n /** Get a single project by ID */\n get: (id: string): Promise<Project> =>\n 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\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> =>\n 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\n // ============ Sessions ============\n sessions: {\n /** List all sessions */\n list: (params: SessionListParams = {}): 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\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: (params: WebhookListParams = {}): Promise<PaginatedResponse<Webhook>> =>\n request(\"GET\", `/webhooks${buildQueryString(params)}`),\n\n /** Get a single webhook by ID */\n get: (id: string): Promise<Webhook> =>\n 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 // ============ Timer ============\n timer: {\n /** Get current timer state */\n get: (): Promise<TimerState> =>\n 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> =>\n request(\"POST\", \"/timer/pause\"),\n\n /** Resume a paused timer */\n continue: (): Promise<TimerState> =>\n request(\"POST\", \"/timer/continue\"),\n\n /** Stop timer and save sessions */\n stop: (): Promise<StopTimerResponse> =>\n request(\"POST\", \"/timer/stop\"),\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 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\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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loculabs/api-client",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "TypeScript client for the Locu REST API",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",