@curless/hotel-storefront 0.23.2 → 0.24.1
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 +35 -11
- package/dist/__tests__/__fixtures__/huazhu-fake.d.ts +2 -0
- package/dist/__tests__/__fixtures__/huazhu-fake.d.ts.map +1 -0
- package/dist/__tests__/__fixtures__/huazhu-fake.js +3 -0
- package/dist/__tests__/__fixtures__/huazhu-fake.js.map +1 -0
- package/dist/__tests__/huazhu-backend.test.d.ts +2 -0
- package/dist/__tests__/huazhu-backend.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-backend.test.js +434 -0
- package/dist/__tests__/huazhu-backend.test.js.map +1 -0
- package/dist/__tests__/huazhu-cancel-policy.test.d.ts +2 -0
- package/dist/__tests__/huazhu-cancel-policy.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-cancel-policy.test.js +82 -0
- package/dist/__tests__/huazhu-cancel-policy.test.js.map +1 -0
- package/dist/__tests__/huazhu-sandbox.test.d.ts +2 -0
- package/dist/__tests__/huazhu-sandbox.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-sandbox.test.js +172 -0
- package/dist/__tests__/huazhu-sandbox.test.js.map +1 -0
- package/dist/__tests__/huazhu-sellable.test.d.ts +2 -0
- package/dist/__tests__/huazhu-sellable.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-sellable.test.js +192 -0
- package/dist/__tests__/huazhu-sellable.test.js.map +1 -0
- package/dist/__tests__/server.test.js +129 -4
- package/dist/__tests__/server.test.js.map +1 -1
- package/dist/__tests__/tool-descriptions.test.js +85 -7
- package/dist/__tests__/tool-descriptions.test.js.map +1 -1
- package/dist/backend.d.ts +27 -0
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +7 -0
- package/dist/backend.js.map +1 -1
- package/dist/card.generated.d.ts +1 -1
- package/dist/card.generated.js +2 -2
- package/dist/card.generated.js.map +1 -1
- package/dist/config.d.ts +64 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/facilities.d.ts.map +1 -1
- package/dist/facilities.js +7 -5
- package/dist/facilities.js.map +1 -1
- package/dist/huazhu/backend.d.ts +40 -0
- package/dist/huazhu/backend.d.ts.map +1 -0
- package/dist/huazhu/backend.js +1318 -0
- package/dist/huazhu/backend.js.map +1 -0
- package/dist/huazhu/bookings.d.ts +81 -0
- package/dist/huazhu/bookings.d.ts.map +1 -0
- package/dist/huazhu/bookings.js +189 -0
- package/dist/huazhu/bookings.js.map +1 -0
- package/dist/huazhu/cancel-policy.d.ts +69 -0
- package/dist/huazhu/cancel-policy.d.ts.map +1 -0
- package/dist/huazhu/cancel-policy.js +201 -0
- package/dist/huazhu/cancel-policy.js.map +1 -0
- package/dist/huazhu/catalog.d.ts +75 -0
- package/dist/huazhu/catalog.d.ts.map +1 -0
- package/dist/huazhu/catalog.js +251 -0
- package/dist/huazhu/catalog.js.map +1 -0
- package/dist/huazhu/check.d.ts +5 -0
- package/dist/huazhu/check.d.ts.map +1 -0
- package/dist/huazhu/check.js +170 -0
- package/dist/huazhu/check.js.map +1 -0
- package/dist/huazhu/client.d.ts +100 -0
- package/dist/huazhu/client.d.ts.map +1 -0
- package/dist/huazhu/client.js +232 -0
- package/dist/huazhu/client.js.map +1 -0
- package/dist/huazhu/fake.d.ts +64 -0
- package/dist/huazhu/fake.d.ts.map +1 -0
- package/dist/huazhu/fake.js +365 -0
- package/dist/huazhu/fake.js.map +1 -0
- package/dist/huazhu/sandbox.d.ts +26 -0
- package/dist/huazhu/sandbox.d.ts.map +1 -0
- package/dist/huazhu/sandbox.js +289 -0
- package/dist/huazhu/sandbox.js.map +1 -0
- package/dist/huazhu/sellable.d.ts +81 -0
- package/dist/huazhu/sellable.d.ts.map +1 -0
- package/dist/huazhu/sellable.js +219 -0
- package/dist/huazhu/sellable.js.map +1 -0
- package/dist/huazhu/wire.d.ts +389 -0
- package/dist/huazhu/wire.d.ts.map +1 -0
- package/dist/huazhu/wire.js +72 -0
- package/dist/huazhu/wire.js.map +1 -0
- package/dist/index.js +18 -4
- package/dist/index.js.map +1 -1
- package/dist/legal.d.ts +4 -3
- package/dist/legal.d.ts.map +1 -1
- package/dist/legal.js +71 -30
- package/dist/legal.js.map +1 -1
- package/dist/lib.d.ts +8 -3
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +8 -1
- package/dist/lib.js.map +1 -1
- package/dist/server.d.ts +20 -8
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +132 -28
- package/dist/server.js.map +1 -1
- package/dist/surface.d.ts +2 -1
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +42 -10
- package/dist/surface.js.map +1 -1
- package/dist/tools.d.ts +33 -20
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +122 -11
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +21 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/vocab.d.ts +5 -0
- package/dist/vocab.d.ts.map +1 -1
- package/dist/vocab.js +2 -1
- package/dist/vocab.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { addDays, todayAt } from '../dates.js';
|
|
2
|
+
import { parseCancelPolicy } from './cancel-policy.js';
|
|
3
|
+
import { HuazhuClient, HuazhuRefusal, HuazhuUnreachable } from './client.js';
|
|
4
|
+
import { clockAt, offeredAtAll, sell } from './sellable.js';
|
|
5
|
+
export const checkHuazhu = async (api, env, out = (s) => console.log(s), fetchImpl) => {
|
|
6
|
+
const prefix = api.envPrefix ?? 'HUAZHU';
|
|
7
|
+
const need = ['CLIENT_ID', 'CLIENT_SECRET', 'DISTRIBUTOR_ID'].map((k) => `${prefix}_${k}`);
|
|
8
|
+
const missing = need.filter((n) => !env[n]);
|
|
9
|
+
if (missing.length) {
|
|
10
|
+
out(`✗ not set: ${missing.join(', ')}`);
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const client = new HuazhuClient({
|
|
14
|
+
baseUrl: api.baseUrl,
|
|
15
|
+
authUrl: api.authUrl,
|
|
16
|
+
credentials: {
|
|
17
|
+
clientId: String(env[`${prefix}_CLIENT_ID`]),
|
|
18
|
+
secret: String(env[`${prefix}_CLIENT_SECRET`]),
|
|
19
|
+
distributorId: String(env[`${prefix}_DISTRIBUTOR_ID`]),
|
|
20
|
+
},
|
|
21
|
+
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
|
22
|
+
});
|
|
23
|
+
const tz = api.timezone ?? 'Asia/Shanghai';
|
|
24
|
+
const step = async (what, run) => {
|
|
25
|
+
try {
|
|
26
|
+
const v = await run();
|
|
27
|
+
return v;
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
if (err instanceof HuazhuRefusal)
|
|
31
|
+
out(`✗ ${what}: Huazhu said ${err.code} ${err.huazhuMessage}${err.detail ? ` - ${err.detail}` : ''}`);
|
|
32
|
+
else if (err instanceof HuazhuUnreachable)
|
|
33
|
+
out(`✗ ${what}: no answer (${err.why}) - is this machine's IP on Huazhu's list?`);
|
|
34
|
+
else
|
|
35
|
+
out(`✗ ${what}: ${err.message}`);
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
out(`Huazhu at ${api.baseUrl} (token from ${api.authUrl}), distributor ${env[`${prefix}_DISTRIBUTOR_ID`]}`);
|
|
40
|
+
const list = await step('hotel list', () => client.hotels());
|
|
41
|
+
if (!list)
|
|
42
|
+
return false;
|
|
43
|
+
out(`✓ token issued, and /hotels lists ${list.length} hotel(s)`);
|
|
44
|
+
const wanted = api.hotels?.length
|
|
45
|
+
? list.filter((h) => api.hotels?.includes(String(h.hotelId)))
|
|
46
|
+
: list;
|
|
47
|
+
if (api.hotels?.length)
|
|
48
|
+
out(` ${wanted.length} of them are in this deployment's hotels list`);
|
|
49
|
+
const first = wanted[0];
|
|
50
|
+
if (!first) {
|
|
51
|
+
out('✗ no hotel to check with');
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const hotel = await step(`hotel ${first.hotelId}`, () => client.hotel(String(first.hotelId)));
|
|
55
|
+
if (!hotel)
|
|
56
|
+
return false;
|
|
57
|
+
const photos = hotel.hotelPhotoList ?? [];
|
|
58
|
+
out(`✓ ${hotel.hotelName} (${hotel.hotelNameEn ?? 'no English name'}) · ${hotel.brandName} · ${hotel.cityName} ${hotel.countyName ?? ''} · ${hotel.tradeCurrency ?? 'no currency given'} · international=${hotel.international}`);
|
|
59
|
+
out(` ${hotel.hotelRoomTypeList?.length ?? 0} room type(s), ${hotel.hotelServiceList?.length ?? 0} facilities, ${photos.length} photo(s); photo hosts: ${[
|
|
60
|
+
...new Set(photos.map((p) => {
|
|
61
|
+
try {
|
|
62
|
+
return new URL(String(p.fileUrl)).origin;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return '?';
|
|
66
|
+
}
|
|
67
|
+
})),
|
|
68
|
+
].join(', ') || 'none'}`);
|
|
69
|
+
if (typeof hotel.openingDate === 'number')
|
|
70
|
+
out(" note: dates arrive as epoch milliseconds, as in the documentation's example");
|
|
71
|
+
const products = await step('products', () => client.products(hotel.hotelId));
|
|
72
|
+
if (!products)
|
|
73
|
+
return false;
|
|
74
|
+
const all = products.products ?? [];
|
|
75
|
+
const allow = new Set(api.allowTags ?? []);
|
|
76
|
+
const offered = all.filter((p) => offeredAtAll(p, allow));
|
|
77
|
+
out(`✓ ${all.length} product(s), ${offered.length} offered here (hour rooms and gated rates left out); sellDays ${products.sellDays}`);
|
|
78
|
+
const tags = [...new Set(all.flatMap((p) => p.tags ?? []))];
|
|
79
|
+
if (tags.length)
|
|
80
|
+
out(` tags seen: ${tags.join(', ')}`);
|
|
81
|
+
const today = todayAt(Date.now(), tz);
|
|
82
|
+
const start = addDays(today, 14);
|
|
83
|
+
const end = addDays(start, 7);
|
|
84
|
+
const [offers, inventory] = await Promise.all([
|
|
85
|
+
step('offers', () => client.offers(hotel.hotelId, start, end)),
|
|
86
|
+
step('inventory', () => client.inventory(hotel.hotelId, start, end)),
|
|
87
|
+
]);
|
|
88
|
+
if (!offers || !inventory)
|
|
89
|
+
return false;
|
|
90
|
+
out(`✓ offers for ${start}..${end}: ${offers.dailyOffers.length} product(s); inventory: ${inventory.dailyInventories.length}`);
|
|
91
|
+
const codes = new Set(offers.dailyOffers.flatMap((o) => o.cancelPolicies.map((c) => c?.code)).filter(Boolean));
|
|
92
|
+
const unread = [...codes].filter((c) => !parseCancelPolicy(c));
|
|
93
|
+
out(` cancellation codes: ${[...codes].join(', ') || 'none'}`);
|
|
94
|
+
if (unread.length)
|
|
95
|
+
out(`✗ these cancellation codes cannot be read here - they would be shown as "the hotel decides": ${unread.join(', ')}`);
|
|
96
|
+
const guarantees = new Set(offers.dailyOffers.flatMap((o) => o.guaranteePolicies.map((g) => g?.code)));
|
|
97
|
+
out(` guarantee codes: ${[...guarantees].join(', ')} - this deployment books as ${api.paymentType ?? 'NGPL'}`);
|
|
98
|
+
const checkIn = start;
|
|
99
|
+
const checkOut = addDays(start, 1);
|
|
100
|
+
const context = {
|
|
101
|
+
checkIn,
|
|
102
|
+
checkOut,
|
|
103
|
+
adults: 2,
|
|
104
|
+
rooms: 1,
|
|
105
|
+
today,
|
|
106
|
+
clock: clockAt(Date.now(), tz),
|
|
107
|
+
paymentType: api.paymentType ?? 'NGPL',
|
|
108
|
+
allowTags: allow,
|
|
109
|
+
};
|
|
110
|
+
const key = (o) => `${o.roomTypeCode}~${o.rateCode}`;
|
|
111
|
+
let cheapest;
|
|
112
|
+
const reasons = new Map();
|
|
113
|
+
for (const p of offered) {
|
|
114
|
+
const offer = offers.dailyOffers.find((o) => key(o) === key(p));
|
|
115
|
+
if (!offer) {
|
|
116
|
+
reasons.set('no offer', (reasons.get('no offer') ?? 0) + 1);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const w = {
|
|
120
|
+
start,
|
|
121
|
+
offer,
|
|
122
|
+
inventory: inventory.dailyInventories.find((i) => key(i) === key(p))?.inventories,
|
|
123
|
+
};
|
|
124
|
+
const res = sell(p, w, hotel.hotelRoomTypeList?.find((r) => r.roomTypeCode === p.roomTypeCode), context);
|
|
125
|
+
if (!res.ok) {
|
|
126
|
+
reasons.set(res.kind, (reasons.get(res.kind) ?? 0) + 1);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const total = res.after.reduce((a, b) => a + b, 0);
|
|
130
|
+
if (!cheapest || total < cheapest.after.reduce((a, b) => a + b, 0)) {
|
|
131
|
+
cheapest = {
|
|
132
|
+
product: p,
|
|
133
|
+
after: res.after,
|
|
134
|
+
...(res.before ? { before: res.before } : {}),
|
|
135
|
+
currency: res.currency,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (reasons.size)
|
|
140
|
+
out(` not sellable for ${checkIn}, 1 night, 2 adults: ${[...reasons].map(([k, n]) => `${n} ${k}`).join(', ')}`);
|
|
141
|
+
if (!cheapest) {
|
|
142
|
+
out('✗ nothing here is sellable for that night - try another hotel, or check the payment type the contract allows');
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
out(`✓ cheapest sellable: ${cheapest.product.roomTypeName} / ${cheapest.product.rateCode} at ${cheapest.after[0]} ${cheapest.currency}`);
|
|
146
|
+
const pre = await step('prebook', () => client.prebook({
|
|
147
|
+
hotelId: hotel.hotelId,
|
|
148
|
+
checkinType: 'DAY',
|
|
149
|
+
paymentType: api.paymentType ?? 'NGPL',
|
|
150
|
+
reservations: [
|
|
151
|
+
{
|
|
152
|
+
roomTypeCode: cheapest.product.roomTypeCode,
|
|
153
|
+
rateCode: cheapest.product.rateCode,
|
|
154
|
+
checkin: checkIn,
|
|
155
|
+
checkout: checkOut,
|
|
156
|
+
amountAfterTax: cheapest.after,
|
|
157
|
+
...(cheapest.before ? { amountBeforeTax: cheapest.before } : {}),
|
|
158
|
+
currencyCode: cheapest.currency,
|
|
159
|
+
roomCount: 1,
|
|
160
|
+
adultCount: 2,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
}));
|
|
164
|
+
if (!pre)
|
|
165
|
+
return false;
|
|
166
|
+
out(`✓ prebook accepted our price${pre.preBookToken ? ' and issued a token' : ' (no token returned)'} - nothing was booked`);
|
|
167
|
+
out('All read-only steps passed.');
|
|
168
|
+
return true;
|
|
169
|
+
};
|
|
170
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/huazhu/check.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type BookContent, type BookRequest, type CancelContent, type ConsistencyContent, type HotelListItem, type HotelStatic, type InventoryContent, type OffersContent, type PrebookContent, type PrebookRequest, type ProductCandidate, type ProductsContent, type ReservationDetail } from './wire.js';
|
|
2
|
+
export type HuazhuCredentials = {
|
|
3
|
+
clientId: string;
|
|
4
|
+
secret: string;
|
|
5
|
+
/** "渠道Code" - issued by Huazhu with the client id. */
|
|
6
|
+
distributorId: string;
|
|
7
|
+
};
|
|
8
|
+
export type HuazhuClientOptions = {
|
|
9
|
+
/** The business endpoints: https://<bizdomain> - /hotels, /ari/..., /reservation/... */
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
/** The token issuer: https://<authservicedomain> - POST /oauth/token. */
|
|
12
|
+
authUrl: string;
|
|
13
|
+
credentials: HuazhuCredentials;
|
|
14
|
+
fetch?: typeof fetch;
|
|
15
|
+
now?: () => number;
|
|
16
|
+
/** Milliseconds before an ordinary call is given up on. */
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
};
|
|
19
|
+
/** Huazhu answered, and the answer was no. Nothing was done. */
|
|
20
|
+
export declare class HuazhuRefusal extends Error {
|
|
21
|
+
readonly code: number;
|
|
22
|
+
readonly huazhuMessage: string;
|
|
23
|
+
readonly detail: string;
|
|
24
|
+
readonly path: string;
|
|
25
|
+
/** A refusal can still carry content - a failed prebook carries today's price. */
|
|
26
|
+
readonly content?: unknown | undefined;
|
|
27
|
+
constructor(code: number, huazhuMessage: string, detail: string, path: string,
|
|
28
|
+
/** A refusal can still carry content - a failed prebook carries today's price. */
|
|
29
|
+
content?: unknown | undefined);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* We do not know what Huazhu did. The documentation's definition: connect or
|
|
33
|
+
* read timeout, a reset, or an HTTP status that is neither 200 nor 4xx.
|
|
34
|
+
*/
|
|
35
|
+
export declare class HuazhuUnreachable extends Error {
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly why: string;
|
|
38
|
+
constructor(path: string, why: string);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The token, shared by every call this process makes.
|
|
42
|
+
*
|
|
43
|
+
* The documentation's rules, each of which is here:
|
|
44
|
+
* - a token lasts `expires_in` seconds (3600 by default);
|
|
45
|
+
* - asking for a new one retires the old one, after a 120-second grace -
|
|
46
|
+
* so two callers that each fetch their own keep invalidating each other;
|
|
47
|
+
* ONE fetch at a time, and everyone waits on it;
|
|
48
|
+
* - refresh early, at a random 180-300 seconds before expiry, so several
|
|
49
|
+
* instances do not all refresh at the same moment.
|
|
50
|
+
*/
|
|
51
|
+
export declare class TokenSource {
|
|
52
|
+
private readonly opts;
|
|
53
|
+
private token?;
|
|
54
|
+
private pending?;
|
|
55
|
+
constructor(opts: Pick<HuazhuClientOptions, 'authUrl' | 'credentials' | 'now'> & {
|
|
56
|
+
fetch: typeof fetch;
|
|
57
|
+
timeoutMs: number;
|
|
58
|
+
});
|
|
59
|
+
private now;
|
|
60
|
+
get(): Promise<string>;
|
|
61
|
+
/** A new token, whatever the old one's clock says - after Huazhu answered 2002. */
|
|
62
|
+
refresh(): Promise<string>;
|
|
63
|
+
private fetchToken;
|
|
64
|
+
}
|
|
65
|
+
export declare class HuazhuClient {
|
|
66
|
+
private readonly opts;
|
|
67
|
+
private readonly fetch;
|
|
68
|
+
private readonly tokens;
|
|
69
|
+
private readonly timeoutMs;
|
|
70
|
+
constructor(opts: HuazhuClientOptions);
|
|
71
|
+
/**
|
|
72
|
+
* One call. Answers the content of a `code: 1000`; throws `HuazhuRefusal`
|
|
73
|
+
* for any other code and `HuazhuUnreachable` when there was no answer.
|
|
74
|
+
*
|
|
75
|
+
* A 2002 (token refused) is retried ONCE with a fresh token - the token can
|
|
76
|
+
* be retired under us by another instance's refresh. Nothing else is
|
|
77
|
+
* retried here: whether a call may be repeated is its caller's to know.
|
|
78
|
+
*/
|
|
79
|
+
call<T>(method: 'GET' | 'POST', path: string, body?: unknown, opts?: {
|
|
80
|
+
timeoutMs?: number;
|
|
81
|
+
}): Promise<T>;
|
|
82
|
+
private once;
|
|
83
|
+
hotels(): Promise<HotelListItem[]>;
|
|
84
|
+
hotel(hotelId: string): Promise<HotelStatic>;
|
|
85
|
+
products(hotelId: string): Promise<ProductsContent>;
|
|
86
|
+
/** At most 30 dates, both ends included - `ariRanges` cuts a longer span. */
|
|
87
|
+
offers(hotelId: string, startDate: string, endDate: string, productCandidates?: ProductCandidate[]): Promise<OffersContent>;
|
|
88
|
+
inventory(hotelId: string, startDate: string, endDate: string, productCandidates?: ProductCandidate[]): Promise<InventoryContent>;
|
|
89
|
+
prebook(req: PrebookRequest): Promise<PrebookContent>;
|
|
90
|
+
/**
|
|
91
|
+
* Make the booking. Throws `HuazhuUnreachable` when the outcome is unknown -
|
|
92
|
+
* see `Reconciler` for what has to happen next, and within what time.
|
|
93
|
+
*/
|
|
94
|
+
book(req: BookRequest): Promise<BookContent>;
|
|
95
|
+
detail(resId: string): Promise<ReservationDetail>;
|
|
96
|
+
cancel(resId: string): Promise<CancelContent>;
|
|
97
|
+
/** "仅限网络异常时调用" - only after /reservation/book went unanswered. */
|
|
98
|
+
consistency(distributorResId: string): Promise<ConsistencyContent>;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/huazhu/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { randomInt, randomUUID } from 'node:crypto';
|
|
2
|
+
import { CODES, SUCCESS, } from './wire.js';
|
|
3
|
+
/** Huazhu answered, and the answer was no. Nothing was done. */
|
|
4
|
+
export class HuazhuRefusal extends Error {
|
|
5
|
+
code;
|
|
6
|
+
huazhuMessage;
|
|
7
|
+
detail;
|
|
8
|
+
path;
|
|
9
|
+
content;
|
|
10
|
+
constructor(code, huazhuMessage, detail, path,
|
|
11
|
+
/** A refusal can still carry content - a failed prebook carries today's price. */
|
|
12
|
+
content) {
|
|
13
|
+
super(`Huazhu ${path} ${code} ${huazhuMessage}${detail ? `: ${detail}` : ''}`);
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.huazhuMessage = huazhuMessage;
|
|
16
|
+
this.detail = detail;
|
|
17
|
+
this.path = path;
|
|
18
|
+
this.content = content;
|
|
19
|
+
this.name = 'HuazhuRefusal';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* We do not know what Huazhu did. The documentation's definition: connect or
|
|
24
|
+
* read timeout, a reset, or an HTTP status that is neither 200 nor 4xx.
|
|
25
|
+
*/
|
|
26
|
+
export class HuazhuUnreachable extends Error {
|
|
27
|
+
path;
|
|
28
|
+
why;
|
|
29
|
+
constructor(path, why) {
|
|
30
|
+
super(`Huazhu ${path} did not answer: ${why}`);
|
|
31
|
+
this.path = path;
|
|
32
|
+
this.why = why;
|
|
33
|
+
this.name = 'HuazhuUnreachable';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The token, shared by every call this process makes.
|
|
38
|
+
*
|
|
39
|
+
* The documentation's rules, each of which is here:
|
|
40
|
+
* - a token lasts `expires_in` seconds (3600 by default);
|
|
41
|
+
* - asking for a new one retires the old one, after a 120-second grace -
|
|
42
|
+
* so two callers that each fetch their own keep invalidating each other;
|
|
43
|
+
* ONE fetch at a time, and everyone waits on it;
|
|
44
|
+
* - refresh early, at a random 180-300 seconds before expiry, so several
|
|
45
|
+
* instances do not all refresh at the same moment.
|
|
46
|
+
*/
|
|
47
|
+
export class TokenSource {
|
|
48
|
+
opts;
|
|
49
|
+
token;
|
|
50
|
+
pending;
|
|
51
|
+
constructor(opts) {
|
|
52
|
+
this.opts = opts;
|
|
53
|
+
}
|
|
54
|
+
now() {
|
|
55
|
+
return this.opts.now?.() ?? Date.now();
|
|
56
|
+
}
|
|
57
|
+
async get() {
|
|
58
|
+
if (this.token && this.now() < this.token.refreshAt)
|
|
59
|
+
return this.token.value;
|
|
60
|
+
return this.refresh();
|
|
61
|
+
}
|
|
62
|
+
/** A new token, whatever the old one's clock says - after Huazhu answered 2002. */
|
|
63
|
+
refresh() {
|
|
64
|
+
if (!this.pending) {
|
|
65
|
+
this.pending = this.fetchToken().finally(() => {
|
|
66
|
+
this.pending = undefined;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return this.pending;
|
|
70
|
+
}
|
|
71
|
+
async fetchToken() {
|
|
72
|
+
const { clientId, secret } = this.opts.credentials;
|
|
73
|
+
const url = `${this.opts.authUrl.replace(/\/$/, '')}/oauth/token?scope=ALL&grant_type=client_credentials`;
|
|
74
|
+
let res;
|
|
75
|
+
try {
|
|
76
|
+
res = await this.opts.fetch(url, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: {
|
|
79
|
+
Authorization: `Basic ${Buffer.from(`${clientId}:${secret}`).toString('base64')}`,
|
|
80
|
+
},
|
|
81
|
+
signal: AbortSignal.timeout(this.opts.timeoutMs),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
throw new HuazhuUnreachable('/oauth/token', err.message);
|
|
86
|
+
}
|
|
87
|
+
const body = (await res.json().catch(() => ({})));
|
|
88
|
+
if (!res.ok || !body.access_token) {
|
|
89
|
+
// The one place a 4xx is expected: a wrong client id or secret.
|
|
90
|
+
throw new HuazhuRefusal(CODES.invalidToken, body.error ?? `HTTP ${res.status}`, body.error_description ?? 'the token was refused - check the client id and secret', '/oauth/token');
|
|
91
|
+
}
|
|
92
|
+
const lifeMs = Math.max(60, Number(body.expires_in) || 3600) * 1000;
|
|
93
|
+
const early = randomInt(180, 301) * 1000;
|
|
94
|
+
this.token = {
|
|
95
|
+
value: body.access_token,
|
|
96
|
+
// A token shorter-lived than the early margin is refreshed at half-life.
|
|
97
|
+
refreshAt: this.now() + (lifeMs > early * 2 ? lifeMs - early : lifeMs / 2),
|
|
98
|
+
};
|
|
99
|
+
return body.access_token;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export class HuazhuClient {
|
|
103
|
+
opts;
|
|
104
|
+
fetch;
|
|
105
|
+
tokens;
|
|
106
|
+
timeoutMs;
|
|
107
|
+
constructor(opts) {
|
|
108
|
+
this.opts = opts;
|
|
109
|
+
this.fetch = opts.fetch ?? globalThis.fetch;
|
|
110
|
+
this.timeoutMs = opts.timeoutMs ?? 10_000;
|
|
111
|
+
this.tokens = new TokenSource({
|
|
112
|
+
authUrl: opts.authUrl,
|
|
113
|
+
credentials: opts.credentials,
|
|
114
|
+
now: opts.now,
|
|
115
|
+
fetch: this.fetch,
|
|
116
|
+
timeoutMs: this.timeoutMs,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* One call. Answers the content of a `code: 1000`; throws `HuazhuRefusal`
|
|
121
|
+
* for any other code and `HuazhuUnreachable` when there was no answer.
|
|
122
|
+
*
|
|
123
|
+
* A 2002 (token refused) is retried ONCE with a fresh token - the token can
|
|
124
|
+
* be retired under us by another instance's refresh. Nothing else is
|
|
125
|
+
* retried here: whether a call may be repeated is its caller's to know.
|
|
126
|
+
*/
|
|
127
|
+
async call(method, path, body, opts = {}) {
|
|
128
|
+
try {
|
|
129
|
+
return await this.once(method, path, body, opts, await this.tokens.get());
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
if (err instanceof HuazhuRefusal && err.code === CODES.invalidToken) {
|
|
133
|
+
return this.once(method, path, body, opts, await this.tokens.refresh());
|
|
134
|
+
}
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async once(method, path, body, opts, token) {
|
|
139
|
+
const url = `${this.opts.baseUrl.replace(/\/$/, '')}${path}`;
|
|
140
|
+
let res;
|
|
141
|
+
try {
|
|
142
|
+
res = await this.fetch(url, {
|
|
143
|
+
method,
|
|
144
|
+
headers: {
|
|
145
|
+
'Content-Type': 'application/json',
|
|
146
|
+
distributorId: this.opts.credentials.distributorId,
|
|
147
|
+
timestamp: String(this.opts.now?.() ?? Date.now()),
|
|
148
|
+
traceId: randomUUID(),
|
|
149
|
+
Authorization: `Bearer ${token}`,
|
|
150
|
+
},
|
|
151
|
+
...(method === 'POST' ? { body: JSON.stringify(body ?? {}) } : {}),
|
|
152
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? this.timeoutMs),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
throw new HuazhuUnreachable(path, err.message);
|
|
157
|
+
}
|
|
158
|
+
// ⚠️ THE DOCUMENTATION'S LINE: "Http状态码非200且非4XX" is a network error.
|
|
159
|
+
// A 4xx is an answer (our request was wrong); a 5xx or a proxy's 502 is
|
|
160
|
+
// not, and must be treated as "it may have happened".
|
|
161
|
+
if (res.status !== 200 && (res.status < 400 || res.status >= 500)) {
|
|
162
|
+
throw new HuazhuUnreachable(path, `HTTP ${res.status}`);
|
|
163
|
+
}
|
|
164
|
+
let envelope;
|
|
165
|
+
try {
|
|
166
|
+
envelope = (await res.json());
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
// A 200 whose body did not arrive whole is no more an answer than a
|
|
170
|
+
// timeout is.
|
|
171
|
+
if (res.status === 200)
|
|
172
|
+
throw new HuazhuUnreachable(path, 'the answer could not be read');
|
|
173
|
+
throw new HuazhuRefusal(res.status, `HTTP ${res.status}`, err.message, path);
|
|
174
|
+
}
|
|
175
|
+
if (res.status !== 200 || envelope.code !== SUCCESS) {
|
|
176
|
+
throw new HuazhuRefusal(Number(envelope.code) || res.status, envelope.message ?? `HTTP ${res.status}`, envelope.responseDes ?? '', path, envelope.content);
|
|
177
|
+
}
|
|
178
|
+
return envelope.content;
|
|
179
|
+
}
|
|
180
|
+
// ------------------------------------------------------------ endpoints ----
|
|
181
|
+
hotels() {
|
|
182
|
+
return this.call('GET', '/hotels').then((x) => x ?? []);
|
|
183
|
+
}
|
|
184
|
+
hotel(hotelId) {
|
|
185
|
+
return this.call('POST', '/hotel', { hotelId });
|
|
186
|
+
}
|
|
187
|
+
products(hotelId) {
|
|
188
|
+
return this.call('POST', '/ari/products', { hotelId });
|
|
189
|
+
}
|
|
190
|
+
/** At most 30 dates, both ends included - `ariRanges` cuts a longer span. */
|
|
191
|
+
offers(hotelId, startDate, endDate, productCandidates) {
|
|
192
|
+
return this.call('POST', '/ari/daily/offers', {
|
|
193
|
+
hotelId,
|
|
194
|
+
startDate,
|
|
195
|
+
endDate,
|
|
196
|
+
checkinType: 'DAY',
|
|
197
|
+
...(productCandidates?.length ? { productCandidates } : {}),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
inventory(hotelId, startDate, endDate, productCandidates) {
|
|
201
|
+
return this.call('POST', '/ari/daily/inventory', {
|
|
202
|
+
hotelId,
|
|
203
|
+
startDate,
|
|
204
|
+
endDate,
|
|
205
|
+
checkinType: 'DAY',
|
|
206
|
+
...(productCandidates?.length ? { productCandidates } : {}),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
prebook(req) {
|
|
210
|
+
return this.call('POST', '/reservation/prebook', req, { timeoutMs: 15_000 });
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Make the booking. Throws `HuazhuUnreachable` when the outcome is unknown -
|
|
214
|
+
* see `Reconciler` for what has to happen next, and within what time.
|
|
215
|
+
*/
|
|
216
|
+
book(req) {
|
|
217
|
+
return this.call('POST', '/reservation/book', req, { timeoutMs: 20_000 });
|
|
218
|
+
}
|
|
219
|
+
detail(resId) {
|
|
220
|
+
return this.call('POST', '/reservation/detail', { resId });
|
|
221
|
+
}
|
|
222
|
+
cancel(resId) {
|
|
223
|
+
return this.call('POST', '/reservation/cancel', { resId }, { timeoutMs: 15_000 });
|
|
224
|
+
}
|
|
225
|
+
/** "仅限网络异常时调用" - only after /reservation/book went unanswered. */
|
|
226
|
+
consistency(distributorResId) {
|
|
227
|
+
return this.call('POST', '/reservation/consistency/query', {
|
|
228
|
+
distributorResId,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/huazhu/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { BookRequest, DailyOffer, HotelStatic, OrderStatus, Product } from './wire.js';
|
|
2
|
+
export type FakeRate = {
|
|
3
|
+
product: Product;
|
|
4
|
+
/** Taxes in, per room-night, for up to `adults` adults. */
|
|
5
|
+
price: (date: string) => number;
|
|
6
|
+
before?: (date: string) => number;
|
|
7
|
+
adults?: number;
|
|
8
|
+
inventory?: (date: string) => number;
|
|
9
|
+
guarantee?: string;
|
|
10
|
+
cancel?: string;
|
|
11
|
+
meal?: {
|
|
12
|
+
type: string;
|
|
13
|
+
count: number;
|
|
14
|
+
};
|
|
15
|
+
restriction?: Partial<Record<keyof DailyOffer['restriction'], (date: string) => unknown>>;
|
|
16
|
+
};
|
|
17
|
+
/** A failure injected on `path`: after `skip` good calls, `times` times (default once). */
|
|
18
|
+
type Fail = {
|
|
19
|
+
path: string;
|
|
20
|
+
kind: 'unreachable' | 'http502' | 'answer-lost' | number;
|
|
21
|
+
times?: number;
|
|
22
|
+
skip?: number;
|
|
23
|
+
};
|
|
24
|
+
export type FakeBooking = {
|
|
25
|
+
resId: string;
|
|
26
|
+
distributorResId: string;
|
|
27
|
+
request: BookRequest;
|
|
28
|
+
status: OrderStatus;
|
|
29
|
+
cancelCode: string;
|
|
30
|
+
};
|
|
31
|
+
/** What the fake remembers between runs, for a sandbox that outlives a restart. */
|
|
32
|
+
export type FakeState = {
|
|
33
|
+
bookings: FakeBooking[];
|
|
34
|
+
held: [string, number][];
|
|
35
|
+
resSeq: number;
|
|
36
|
+
};
|
|
37
|
+
export declare const createFakeHuazhu: (opts: {
|
|
38
|
+
hotels: HotelStatic[];
|
|
39
|
+
rates: Record<string, FakeRate[]>;
|
|
40
|
+
now: () => number;
|
|
41
|
+
distributorId?: string;
|
|
42
|
+
clientId?: string;
|
|
43
|
+
secret?: string;
|
|
44
|
+
/** Bookings and held rooms from an earlier run (see `snapshot`). */
|
|
45
|
+
state?: FakeState;
|
|
46
|
+
}) => {
|
|
47
|
+
fetch: typeof fetch;
|
|
48
|
+
calls: {
|
|
49
|
+
path: string;
|
|
50
|
+
body: unknown;
|
|
51
|
+
headers: Record<string, string>;
|
|
52
|
+
}[];
|
|
53
|
+
bookings: Map<string, FakeBooking>;
|
|
54
|
+
/** Everything `state` takes back, for the sandbox to write down after each call. */
|
|
55
|
+
snapshot: () => FakeState;
|
|
56
|
+
fail: (f: Fail) => number;
|
|
57
|
+
retireToken: () => void;
|
|
58
|
+
readonly tokens: number;
|
|
59
|
+
};
|
|
60
|
+
/** One domestic hotel shaped like the documentation's example. */
|
|
61
|
+
export declare const fakeHotel: (id: string, over?: Partial<HotelStatic>) => HotelStatic;
|
|
62
|
+
export declare const product: (roomTypeCode: string, rateCode: string, over?: Partial<Product>) => Product;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=fake.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake.d.ts","sourceRoot":"","sources":["../../src/huazhu/fake.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,WAAW,EACX,WAAW,EAEX,OAAO,EACR,MAAM,WAAW,CAAC;AAWnB,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAChC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC;CAC3F,CAAC;AAEF,2FAA2F;AAC3F,KAAK,IAAI,GAAG;IACV,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,CAAC;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,mFAAmF;AACnF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,MAAM;IACrC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClC,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;WAmUW,OAAO,KAAK;;cAtTD,MAAM;cAAQ,OAAO;iBAAW,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;;;IAyTzE,oFAAoF;oBACtE,SAAS;cACb,IAAI;;;CAQjB,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,SAAS,GAAI,IAAI,MAAM,EAAE,OAAM,OAAO,CAAC,WAAW,CAAM,KAAG,WAwDtE,CAAC;AAEH,eAAO,MAAM,OAAO,GAClB,cAAc,MAAM,EACpB,UAAU,MAAM,EAChB,OAAM,OAAO,CAAC,OAAO,CAAM,KAC1B,OAQD,CAAC"}
|