@tallyui/connector-woocommerce 3.0.0-next.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.js +13 -11
- package/package.json +3 -3
- package/src/index.ts +1 -1
- package/src/reconcile/catalogue.ts +8 -4
- package/src/replication/products.ts +14 -9
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ The incremental pull can miss an edit: a `modified_after` that over-excludes (in
|
|
|
36
36
|
## Errors
|
|
37
37
|
|
|
38
38
|
- `ConnectorUnauthorizedError`, from `@tallyui/core` and re-exported here: the store refused the request (401: the token is missing, expired or revoked; 403: signed in but not allowed), or `WooMissingTokenError` (a subclass) was thrown for a missing token. `error.status` is `401` (`code: 'unauthorized'`, sign in again) or `403` (`code: 'forbidden'`, signed in but not allowed: the pull retries on the store schedule; don't sign out).
|
|
39
|
-
- `
|
|
39
|
+
- `WooTokenRefusedError` (`code: 'store_misconfigured'`, `fixedBy: 'store'`, `fix`, and the store's own `storeCode`): the store answered a `jwt_auth_*` 403, the JWT Authentication plugin refusing the till's token. WCPOS 1.10.0–1.10.7 sent it for a valid token (wcpos#1863), and later versions can still send it when the token resolves to another user, so the error names no version. The store owner checks the JWT Authentication plugin's settings, or the till is paired again; the pull retries on the store schedule and the till stays signed in.
|
|
40
40
|
- `WooDateFilterError` (`code: 'unsupported_store'`): the store returned a product outside the requested date window, so it does not honour `modified_after` (WooCommerce before 5.8, or a proxy that drops the parameter). An app should show its own words for the `code`; the message, `This store needs WooCommerce 5.8 or later to sync products.`, is a plain fallback. The diagnostics are in `productId`, `bound` (the `modified_after` sent) and `received`. Today the pull retries a failed request every 5 s (RxDB's `retryTime`), one mark request each time. Stopping on errors that cannot recover is shared replication work.
|
|
41
41
|
- `WooTillUpdateRequiredError` (`code: 'till_update_required'`, `fixedBy: 'till'`): the store answered 426, normally WCPOS's protocol gate (`serverCode: 'wcpos_update_required'`), so this till must be updated; the pull pauses until `resume()`.
|
|
42
42
|
- `WooMissingUuidError`: a product came back without a `uuid`, so the store is not running the WCPOS Free plugin 1.10.0 or later, or the connector is not reaching it through `wcpos/v2`.
|
package/dist/index.d.ts
CHANGED
|
@@ -69,14 +69,19 @@ declare class WooDateFilterError extends Error {
|
|
|
69
69
|
readonly minVersion = "5.8";
|
|
70
70
|
constructor(productId: number | undefined, bound: string, received: string | undefined);
|
|
71
71
|
}
|
|
72
|
-
/**
|
|
73
|
-
|
|
72
|
+
/**
|
|
73
|
+
* A `jwt_auth_*` 403: the store's JWT Authentication plugin refused the till's token. WCPOS 1.10.0–1.10.7 sent it for a valid token
|
|
74
|
+
* (wcpos/woocommerce-pos#1863); 1.10.8 and later can still send it when the token resolves to another user. So the
|
|
75
|
+
* message names no version: a version hint belongs only where a response itself carries the WCPOS version (#360).
|
|
76
|
+
*/
|
|
77
|
+
declare class WooTokenRefusedError extends Error {
|
|
74
78
|
readonly storeCode: string;
|
|
75
79
|
name: string;
|
|
76
|
-
readonly code: "
|
|
80
|
+
readonly code: "store_misconfigured";
|
|
81
|
+
/** The store owner fixes it on the store, so the pull waits the store delay and the till stays signed in (`errorKind`). */
|
|
77
82
|
readonly fixedBy: "store";
|
|
78
|
-
|
|
79
|
-
readonly
|
|
83
|
+
/** The store-side remedy, for `SyncStatus`'s detail. */
|
|
84
|
+
readonly fix = "check the JWT Authentication plugin's settings";
|
|
80
85
|
constructor(storeCode: string);
|
|
81
86
|
}
|
|
82
87
|
/**
|
|
@@ -134,4 +139,4 @@ declare function createWooCommerceConnector(): TallyConnector;
|
|
|
134
139
|
*/
|
|
135
140
|
declare const woocommerceConnector: TallyConnector;
|
|
136
141
|
|
|
137
|
-
export { WooDateFilterError, WooMissingTokenError, WooMissingUuidError,
|
|
142
|
+
export { WooDateFilterError, WooMissingTokenError, WooMissingUuidError, WooTillUpdateRequiredError, WooTokenRefusedError, createWooCommerceConnector, wcposClientPart, wooCatalogueReconcile, wooProductReplication, wooProductSchema, wooProductSync, wooProductTraits, wooReconcileFingerprint, woocommerceConnector };
|
package/dist/index.js
CHANGED
|
@@ -260,7 +260,7 @@ async function checkResponse(response) {
|
|
|
260
260
|
if (response.ok) return;
|
|
261
261
|
if (response.status === 401 || response.status === 403) {
|
|
262
262
|
const body = response.status === 403 ? await response.json().catch(() => void 0) : void 0;
|
|
263
|
-
if (typeof body?.code === "string" && body.code.startsWith("jwt_auth_")) throw new
|
|
263
|
+
if (typeof body?.code === "string" && body.code.startsWith("jwt_auth_")) throw new WooTokenRefusedError(body.code);
|
|
264
264
|
throw new ConnectorUnauthorizedError(`WooCommerce API error: ${response.status}`, response.status);
|
|
265
265
|
}
|
|
266
266
|
if (response.status === 426) {
|
|
@@ -315,16 +315,17 @@ var WooDateFilterError = class extends Error {
|
|
|
315
315
|
/** The first WooCommerce version that applies `modified_after` (5.8). */
|
|
316
316
|
minVersion = "5.8";
|
|
317
317
|
};
|
|
318
|
-
var
|
|
318
|
+
var WooTokenRefusedError = class extends Error {
|
|
319
319
|
constructor(storeCode) {
|
|
320
|
-
super("
|
|
320
|
+
super("The store refused the sign-in token (403). Ask the store owner to check the JWT Authentication plugin's settings, or pair the till again.");
|
|
321
321
|
this.storeCode = storeCode;
|
|
322
322
|
}
|
|
323
|
-
name = "
|
|
324
|
-
code = "
|
|
323
|
+
name = "WooTokenRefusedError";
|
|
324
|
+
code = "store_misconfigured";
|
|
325
|
+
/** The store owner fixes it on the store, so the pull waits the store delay and the till stays signed in (`errorKind`). */
|
|
325
326
|
fixedBy = "store";
|
|
326
|
-
|
|
327
|
-
|
|
327
|
+
/** The store-side remedy, for `SyncStatus`'s detail. */
|
|
328
|
+
fix = "check the JWT Authentication plugin's settings";
|
|
328
329
|
};
|
|
329
330
|
var wooProductReplication = {
|
|
330
331
|
pull: {
|
|
@@ -438,6 +439,7 @@ function wooCatalogueReconcile(feed) {
|
|
|
438
439
|
return;
|
|
439
440
|
}
|
|
440
441
|
if (!failed) console.warn("WooCommerce catalogue reconcile: the fast path answered with something that is not a list; listing page by page.");
|
|
442
|
+
yield { entries: [], cursor: from };
|
|
441
443
|
}
|
|
442
444
|
for (let page = from; ; page++) {
|
|
443
445
|
const params = new URLSearchParams({
|
|
@@ -461,8 +463,8 @@ function wooCatalogueReconcile(feed) {
|
|
|
461
463
|
if (!ids.length) return [];
|
|
462
464
|
const params = new URLSearchParams({ include: ids.join(","), per_page: String(PAGE_SIZE), status: "any", _fields: "id,uuid,status" });
|
|
463
465
|
const rows = await get(`/products?${params}`, context);
|
|
464
|
-
const live = new
|
|
465
|
-
return locals.filter((doc) => Number.isInteger(doc.id) && !live.has(doc.id)).map((doc) => doc.uuid);
|
|
466
|
+
const live = new Map(rows.filter((row) => row.status === "publish").map((row) => [row.id, typeof row.uuid === "string" && row.uuid.length > 0 ? row.uuid : void 0]));
|
|
467
|
+
return locals.filter((doc) => Number.isInteger(doc.id) && (!live.has(doc.id) || typeof live.get(doc.id) === "string" && live.get(doc.id) !== doc.uuid)).map((doc) => doc.uuid);
|
|
466
468
|
},
|
|
467
469
|
// An entry with no numeric id, locally or in the listing, is dropped: the store cannot be asked about it, so
|
|
468
470
|
// the feed would find nothing and tombstone it without proof. (The schema requires only uuid.)
|
|
@@ -493,7 +495,7 @@ function createWooReconcileFeed() {
|
|
|
493
495
|
}
|
|
494
496
|
|
|
495
497
|
// package.json
|
|
496
|
-
var version = "3.0.0
|
|
498
|
+
var version = "3.0.0";
|
|
497
499
|
|
|
498
500
|
// src/index.ts
|
|
499
501
|
import { ConnectorUnauthorizedError as ConnectorUnauthorizedError3 } from "@tallyui/core";
|
|
@@ -575,8 +577,8 @@ export {
|
|
|
575
577
|
WooDateFilterError,
|
|
576
578
|
WooMissingTokenError,
|
|
577
579
|
WooMissingUuidError,
|
|
578
|
-
WooPluginUpdateRequiredError,
|
|
579
580
|
WooTillUpdateRequiredError,
|
|
581
|
+
WooTokenRefusedError,
|
|
580
582
|
createWooCommerceConnector,
|
|
581
583
|
wcposClientPart,
|
|
582
584
|
wooCatalogueReconcile,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tallyui/connector-woocommerce",
|
|
3
|
-
"version": "3.0.0
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "WooCommerce connector for Tally UI",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"directory": "connectors/woocommerce"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@tallyui/core": "^3.0.0
|
|
29
|
+
"@tallyui/core": "^3.0.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@tallyui/database": "3.0.0
|
|
32
|
+
"@tallyui/database": "3.0.0",
|
|
33
33
|
"rxdb": "17.5.0"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
package/src/index.ts
CHANGED
|
@@ -119,5 +119,5 @@ export { ConnectorUnauthorizedError } from '@tallyui/core';
|
|
|
119
119
|
export { wooProductSchema } from './schemas/products';
|
|
120
120
|
export { wooProductTraits } from './traits/product';
|
|
121
121
|
export { wooProductSync } from './sync/products';
|
|
122
|
-
export { wooProductReplication, WooDateFilterError,
|
|
122
|
+
export { wooProductReplication, WooDateFilterError, WooTokenRefusedError, WooMissingUuidError, WooTillUpdateRequiredError } from './replication/products';
|
|
123
123
|
export { wooCatalogueReconcile, wooReconcileFingerprint } from './reconcile/catalogue';
|
|
@@ -32,7 +32,7 @@ async function get(path: string, context: SyncContext): Promise<any> {
|
|
|
32
32
|
* `capabilities` (a missing field is false). Never a version number, and never a failed attempt.
|
|
33
33
|
* A failed status read is "no capability known"; only a 401 or 403 would make the paged listing fail the same way.
|
|
34
34
|
* Only a till-class error (401 `unauthorized`, 426 `till_update_required`) or an abort stops the pass.
|
|
35
|
-
* A 403 `forbidden` is store-class: the
|
|
35
|
+
* A 403 `forbidden` is store-class: the whole pass is logged `skipped` and runs again at the next check (hourly by default).
|
|
36
36
|
*/
|
|
37
37
|
export async function wooHasIdFastPath(context: SyncContext): Promise<boolean> {
|
|
38
38
|
let status: any;
|
|
@@ -86,6 +86,8 @@ export function wooCatalogueReconcile(feed: Pick<ReconcileFeed<any>, 'enqueue'>)
|
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
if (!failed) console.warn('WooCommerce catalogue reconcile: the fast path answered with something that is not a list; listing page by page.');
|
|
89
|
+
// An empty page, so the fallback's first request takes its own budget slot, as the status read does.
|
|
90
|
+
yield { entries: [], cursor: from };
|
|
89
91
|
}
|
|
90
92
|
// status=publish: a product that is not published is absent, so it becomes a deletion candidate and
|
|
91
93
|
// confirmGone decides; drafts cost no daily refetch.
|
|
@@ -106,9 +108,11 @@ export function wooCatalogueReconcile(feed: Pick<ReconcileFeed<any>, 'enqueue'>)
|
|
|
106
108
|
// An empty include= would list the whole store; a local without a numeric id has no proof and is kept.
|
|
107
109
|
if (!ids.length) return [];
|
|
108
110
|
const params = new URLSearchParams({ include: ids.join(','), per_page: String(PAGE_SIZE), status: 'any', _fields: 'id,uuid,status' });
|
|
109
|
-
const rows: Array<{ id: number; status: string }> = await get(`/products?${params}`, context);
|
|
110
|
-
const live = new
|
|
111
|
-
|
|
111
|
+
const rows: Array<{ id: number; uuid: string; status: string }> = await get(`/products?${params}`, context);
|
|
112
|
+
const live = new Map(rows.filter((row) => row.status === 'publish').map((row) => [row.id, typeof row.uuid === 'string' && row.uuid.length > 0 ? row.uuid : undefined]));
|
|
113
|
+
// A live id backs only the store's uuid; other local copies are gone (#369).
|
|
114
|
+
// A row without a uuid can't prove the local copy stale, so a live id keeps it (#375 review).
|
|
115
|
+
return locals.filter((doc) => Number.isInteger(doc.id) && (!live.has(doc.id) || (typeof live.get(doc.id) === 'string' && live.get(doc.id) !== doc.uuid))).map((doc) => doc.uuid);
|
|
112
116
|
},
|
|
113
117
|
// An entry with no numeric id, locally or in the listing, is dropped: the store cannot be asked about it, so
|
|
114
118
|
// the feed would find nothing and tombstone it without proof. (The schema requires only uuid.)
|
|
@@ -21,9 +21,9 @@ const MAX_STORE_MESSAGE = 200;
|
|
|
21
21
|
export async function checkResponse(response: Response) {
|
|
22
22
|
if (response.ok) return;
|
|
23
23
|
if (response.status === 401 || response.status === 403) {
|
|
24
|
-
//
|
|
24
|
+
// A jwt_auth_* 403 is the store's JWT layer refusing the token, on any WCPOS version (#360).
|
|
25
25
|
const body = response.status === 403 ? await response.json().catch(() => undefined) : undefined;
|
|
26
|
-
if (typeof body?.code === 'string' && body.code.startsWith('jwt_auth_')) throw new
|
|
26
|
+
if (typeof body?.code === 'string' && body.code.startsWith('jwt_auth_')) throw new WooTokenRefusedError(body.code);
|
|
27
27
|
throw new ConnectorUnauthorizedError(`WooCommerce API error: ${response.status}`, response.status as 401 | 403);
|
|
28
28
|
}
|
|
29
29
|
if (response.status === 426) {
|
|
@@ -90,16 +90,21 @@ export class WooDateFilterError extends Error {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
/**
|
|
94
|
+
* A `jwt_auth_*` 403: the store's JWT Authentication plugin refused the till's token. WCPOS 1.10.0–1.10.7 sent it for a valid token
|
|
95
|
+
* (wcpos/woocommerce-pos#1863); 1.10.8 and later can still send it when the token resolves to another user. So the
|
|
96
|
+
* message names no version: a version hint belongs only where a response itself carries the WCPOS version (#360).
|
|
97
|
+
*/
|
|
98
|
+
export class WooTokenRefusedError extends Error {
|
|
99
|
+
name = 'WooTokenRefusedError';
|
|
100
|
+
readonly code = 'store_misconfigured' as const;
|
|
101
|
+
/** The store owner fixes it on the store, so the pull waits the store delay and the till stays signed in (`errorKind`). */
|
|
97
102
|
readonly fixedBy = 'store' as const;
|
|
98
|
-
|
|
99
|
-
readonly
|
|
103
|
+
/** The store-side remedy, for `SyncStatus`'s detail. */
|
|
104
|
+
readonly fix = "check the JWT Authentication plugin's settings";
|
|
100
105
|
|
|
101
106
|
constructor(readonly storeCode: string) {
|
|
102
|
-
super(
|
|
107
|
+
super("The store refused the sign-in token (403). Ask the store owner to check the JWT Authentication plugin's settings, or pair the till again.");
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
|