Skip to content

Commit f86f6ae

Browse files
committed
add timestamp support and rate limit info to patch
- Extend bun patch to include createdAt getter, safe lastModifiedAt, timestamps in toJson (createdAt, lastModifiedAt, completedAt) - Add ct field parsing in schema - Add rate limit error handling with retry info Upstream PRs: - Root fix: karelklima/workflowy#10 - Timestamps: karelklima/workflowy#12
1 parent ac4552e commit f86f6ae

File tree

2 files changed

+101
-6
lines changed

2 files changed

+101
-6
lines changed

patches/workflowy@2.8.2.patch

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
11
diff --git a/esm/src/document.js b/esm/src/document.js
2-
index 4d9346c41dad9338fb20175f8b4c1ef94aaf57b3..b13f1555ea1a1ec9e85d6a3c13fa7db3032d2c2b 100644
2+
index 4d9346c..c24ccb9 100644
33
--- a/esm/src/document.js
44
+++ b/esm/src/document.js
5-
@@ -318,7 +318,7 @@ export class List {
5+
@@ -210,9 +210,19 @@ export class List {
6+
get note() {
7+
return this.data.note || "";
8+
}
9+
- /** Date of last change */
10+
+ /** Date of creation, or undefined if not available */
11+
+ get createdAt() {
12+
+ if (this.data.created !== undefined) {
13+
+ return this.#companion.getRealTimestamp(this.data.created);
14+
+ }
15+
+ return undefined;
16+
+ }
17+
+ /** Date of last change, or undefined if not available */
18+
get lastModifiedAt() {
19+
- return this.#companion.getRealTimestamp(this.data.lastModified);
20+
+ if (this.data.lastModified !== undefined) {
21+
+ return this.#companion.getRealTimestamp(this.data.lastModified);
22+
+ }
23+
+ return undefined;
24+
}
25+
/** Date of completion, or undefined if not completed */
26+
get completedAt() {
27+
@@ -318,7 +328,7 @@ export class List {
628
});
729
this.#companion.shortIdMap.set(newId.split("-").pop(), newId);
830
this.itemIds.splice(priority, 0, newId);
@@ -11,3 +33,37 @@ index 4d9346c41dad9338fb20175f8b4c1ef94aaf57b3..b13f1555ea1a1ec9e85d6a3c13fa7db3
1133
this.#companion.addOperation(this.data.treeId, {
1234
type: "create",
1335
data: {
36+
diff --git a/esm/src/export.js b/esm/src/export.js
37+
index 0213a2c..34f7ba6 100644
38+
--- a/esm/src/export.js
39+
+++ b/esm/src/export.js
40+
@@ -53,6 +53,9 @@ export function toJson(list) {
41+
name: list.name,
42+
note: list.note,
43+
isCompleted: list.isCompleted,
44+
+ createdAt: list.createdAt?.toISOString(),
45+
+ lastModifiedAt: list.lastModifiedAt?.toISOString(),
46+
+ completedAt: list.completedAt?.toISOString(),
47+
items: list.items.map((sublist) => toJson(sublist)),
48+
};
49+
}
50+
diff --git a/esm/src/schema.js b/esm/src/schema.js
51+
index f3d3e31..057da4a 100644
52+
--- a/esm/src/schema.js
53+
+++ b/esm/src/schema.js
54+
@@ -48,6 +48,7 @@ export const TreeDataSchema = z.object({
55+
prnt: z.string().or(z.null()),
56+
pr: z.number(),
57+
cp: z.number().optional(),
58+
+ ct: z.number().optional(),
59+
lm: z.number(),
60+
metadata: z.object({
61+
mirror: z.object({
62+
@@ -63,6 +64,7 @@ export const TreeDataSchema = z.object({
63+
parentId: i.prnt !== null ? i.prnt : ROOT,
64+
priority: i.pr,
65+
completed: i.cp,
66+
+ created: i.ct,
67+
lastModified: i.lm,
68+
originalId: i.metadata?.mirror?.originalId,
69+
isMirrorRoot: i.metadata?.mirror?.isMirrorRoot === true,

src/workflowy/client.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,37 @@
11
import { WorkFlowy, Client } from 'workflowy';
22

33
class WorkflowyClient {
4+
/**
5+
* Make a raw request to Workflowy login to get rate limit headers
6+
*/
7+
private async getRateLimitInfo(): Promise<string | null> {
8+
try {
9+
const response = await fetch('https://workflowy.com/ajax_login', {
10+
method: 'POST',
11+
body: new FormData() // Empty form to trigger rate limit response
12+
});
13+
const retryAfter = response.headers.get('Retry-After');
14+
const xRateLimit = response.headers.get('X-RateLimit-Reset');
15+
if (retryAfter) {
16+
return `${retryAfter} seconds`;
17+
}
18+
if (xRateLimit) {
19+
const resetTime = new Date(parseInt(xRateLimit) * 1000);
20+
return resetTime.toLocaleTimeString();
21+
}
22+
// Log all headers for debugging
23+
const headers: string[] = [];
24+
response.headers.forEach((value, key) => {
25+
if (key.toLowerCase().includes('rate') || key.toLowerCase().includes('retry')) {
26+
headers.push(`${key}: ${value}`);
27+
}
28+
});
29+
return headers.length > 0 ? headers.join(', ') : null;
30+
} catch {
31+
return null;
32+
}
33+
}
34+
435
/**
536
* Create authenticated Workflowy client instance
637
* @private Helper method to create and authenticate a Workflowy client
@@ -17,10 +48,18 @@ class WorkflowyClient {
1748
// Create a new Workflowy client instance
1849
const wf = new WorkFlowy(loginUsername, loginPassword);
1950
const client = wf.getClient();
20-
const ok = await client.login();
21-
22-
if (!ok.success) {
23-
throw new Error('Workflowy authentication failed. Please provide valid credentials.');
51+
try {
52+
const ok = await client.login();
53+
if (!ok.success) {
54+
throw new Error('Workflowy authentication failed. Please provide valid credentials.');
55+
}
56+
} catch (e: any) {
57+
// Try to extract rate limit info from the error or make a raw request to get headers
58+
if (e.message?.includes('429')) {
59+
const retryInfo = await this.getRateLimitInfo();
60+
throw new Error(`Rate limited by Workflowy.${retryInfo ? ` Retry after: ${retryInfo}` : ''}`);
61+
}
62+
throw e;
2463
}
2564

2665
return { wf, client };

0 commit comments

Comments
 (0)