@curless/hotel-storefront 0.25.0 → 0.26.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.
Files changed (73) hide show
  1. package/dist/__tests__/__helpers__/sessions.d.ts +10 -0
  2. package/dist/__tests__/__helpers__/sessions.d.ts.map +1 -0
  3. package/dist/__tests__/__helpers__/sessions.js +37 -0
  4. package/dist/__tests__/__helpers__/sessions.js.map +1 -0
  5. package/dist/__tests__/card-contract.test.js +20 -0
  6. package/dist/__tests__/card-contract.test.js.map +1 -1
  7. package/dist/__tests__/data.test.js +7 -1
  8. package/dist/__tests__/data.test.js.map +1 -1
  9. package/dist/__tests__/dates.test.js +13 -1
  10. package/dist/__tests__/dates.test.js.map +1 -1
  11. package/dist/__tests__/huazhu-audit.test.js +7 -1
  12. package/dist/__tests__/huazhu-audit.test.js.map +1 -1
  13. package/dist/__tests__/huazhu-sandbox.test.js +6 -1
  14. package/dist/__tests__/huazhu-sandbox.test.js.map +1 -1
  15. package/dist/__tests__/server.test.js +111 -2
  16. package/dist/__tests__/server.test.js.map +1 -1
  17. package/dist/__tests__/state.test.d.ts +2 -0
  18. package/dist/__tests__/state.test.d.ts.map +1 -0
  19. package/dist/__tests__/state.test.js +204 -0
  20. package/dist/__tests__/state.test.js.map +1 -0
  21. package/dist/__tests__/tools.test.js +18 -0
  22. package/dist/__tests__/tools.test.js.map +1 -1
  23. package/dist/card.generated.d.ts +1 -1
  24. package/dist/card.generated.js +2 -2
  25. package/dist/card.generated.js.map +1 -1
  26. package/dist/dates.d.ts +9 -0
  27. package/dist/dates.d.ts.map +1 -1
  28. package/dist/dates.js +32 -0
  29. package/dist/dates.js.map +1 -1
  30. package/dist/demo-hotels.d.ts +21 -1
  31. package/dist/demo-hotels.d.ts.map +1 -1
  32. package/dist/demo-hotels.js +83 -11
  33. package/dist/demo-hotels.js.map +1 -1
  34. package/dist/index.js +9 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/legal.d.ts +8 -1
  37. package/dist/legal.d.ts.map +1 -1
  38. package/dist/legal.js +22 -8
  39. package/dist/legal.js.map +1 -1
  40. package/dist/members.d.ts +3 -1
  41. package/dist/members.d.ts.map +1 -1
  42. package/dist/members.js +24 -6
  43. package/dist/members.js.map +1 -1
  44. package/dist/money.d.ts.map +1 -1
  45. package/dist/money.js +8 -1
  46. package/dist/money.js.map +1 -1
  47. package/dist/server.d.ts +12 -8
  48. package/dist/server.d.ts.map +1 -1
  49. package/dist/server.js +101 -36
  50. package/dist/server.js.map +1 -1
  51. package/dist/sessions.d.ts +19 -0
  52. package/dist/sessions.d.ts.map +1 -0
  53. package/dist/sessions.js +96 -0
  54. package/dist/sessions.js.map +1 -0
  55. package/dist/state.d.ts +32 -0
  56. package/dist/state.d.ts.map +1 -0
  57. package/dist/state.js +118 -0
  58. package/dist/state.js.map +1 -0
  59. package/dist/tools.d.ts +11 -0
  60. package/dist/tools.d.ts.map +1 -1
  61. package/dist/tools.js +34 -3
  62. package/dist/tools.js.map +1 -1
  63. package/dist/trip-ids.d.ts +8 -1
  64. package/dist/trip-ids.d.ts.map +1 -1
  65. package/dist/trip-ids.js +24 -1
  66. package/dist/trip-ids.js.map +1 -1
  67. package/dist/trip.d.ts +3 -1
  68. package/dist/trip.d.ts.map +1 -1
  69. package/dist/trip.js +23 -6
  70. package/dist/trip.js.map +1 -1
  71. package/dist/types.d.ts +2 -0
  72. package/dist/types.d.ts.map +1 -1
  73. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"card.generated.js","sourceRoot":"","sources":["../src/card.generated.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,wEAAwE;AACxE,gFAAgF;AAChF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,eAAe,GAAW,MAAM,CAAC,IAAI,CAChD,0vlYAA0vlY,EAC1vlY,QAAQ,CACT,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEnB,2FAA2F;AAC3F,MAAM,CAAC,MAAM,qBAAqB,GAChC,kEAAkE,CAAC"}
1
+ {"version":3,"file":"card.generated.js","sourceRoot":"","sources":["../src/card.generated.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,wEAAwE;AACxE,gFAAgF;AAChF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,eAAe,GAAW,MAAM,CAAC,IAAI,CAChD,8wlYAA8wlY,EAC9wlY,QAAQ,CACT,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEnB,2FAA2F;AAC3F,MAAM,CAAC,MAAM,qBAAqB,GAChC,kEAAkE,CAAC"}
package/dist/dates.d.ts CHANGED
@@ -10,6 +10,15 @@ export declare const eachNight: (checkIn: string, checkOut: string) => string[];
10
10
  export declare const isWeekendNight: (day: string) => boolean;
11
11
  /** The hotel's own today, in its own zone (`Europe/Paris`) or offset (`+08:00`). */
12
12
  export declare const todayAt: (now: number, tz: string) => string;
13
+ /**
14
+ * An instant as the hotel's own clock shows it - "Sat 17 Oct, 15:00".
15
+ *
16
+ * ⚠️ A DEADLINE WITHOUT ITS HOUR IS NOT A DEADLINE. A free-cancellation window
17
+ * that closes at 15:00 was shown as "until 2026-10-17", and a guest cancelling
18
+ * that evening would have been charged a night (2026-10-05 audit). In the
19
+ * hotel's time, because that is the clock the desk will hold them to.
20
+ */
21
+ export declare const atHotelTime: (instant: number, tz: string) => string;
13
22
  /** The moment a guest can walk in, as an instant. */
14
23
  export declare const arrivalInstant: (checkIn: string, checkInFrom: string, tz: string) => number;
15
24
  export declare const MAX_NIGHTS = 30;
@@ -1 +1 @@
1
- {"version":3,"file":"dates.d.ts","sourceRoot":"","sources":["../src/dates.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,MAAM,WAAa,CAAC;AAEjC,eAAO,MAAM,KAAK,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,MAIpB,CAAC;AAErB,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,MAAiD,CAAC;AAErF,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,EAAE,GAAG,MAAM,KAAG,MAAqC,CAAC;AAEvF,qEAAqE;AACrE,eAAO,MAAM,MAAM,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,MACR,CAAC;AAEpD,kFAAkF;AAClF,eAAO,MAAM,SAAS,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,MAAM,EAKnE,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,OAG5C,CAAC;AAcF,oFAAoF;AACpF,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,EAAE,IAAI,MAAM,KAAG,MAqBjD,CAAC;AAwBF,qDAAqD;AACrD,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,EAAE,aAAa,MAAM,EAAE,IAAI,MAAM,KAAG,MAMjF,CAAC;AAEF,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,uFAAuF;AACvF,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GACtB,MAAM;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EACjE,OAAO,MAAM,KACZ,MAAM,GAAG,IAiBX,CAAC"}
1
+ {"version":3,"file":"dates.d.ts","sourceRoot":"","sources":["../src/dates.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,MAAM,WAAa,CAAC;AAEjC,eAAO,MAAM,KAAK,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,MAIpB,CAAC;AAErB,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,MAAiD,CAAC;AAErF,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,EAAE,GAAG,MAAM,KAAG,MAAqC,CAAC;AAEvF,qEAAqE;AACrE,eAAO,MAAM,MAAM,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,MACR,CAAC;AAEpD,kFAAkF;AAClF,eAAO,MAAM,SAAS,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,MAAM,EAKnE,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,OAG5C,CAAC;AAcF,oFAAoF;AACpF,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,EAAE,IAAI,MAAM,KAAG,MAqBjD,CAAC;AAwBF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,EAAE,IAAI,MAAM,KAAG,MAwBzD,CAAC;AAEF,qDAAqD;AACrD,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,EAAE,aAAa,MAAM,EAAE,IAAI,MAAM,KAAG,MAMjF,CAAC;AAEF,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,uFAAuF;AACvF,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GACtB,MAAM;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EACjE,OAAO,MAAM,KACZ,MAAM,GAAG,IAiBX,CAAC"}
package/dist/dates.js CHANGED
@@ -86,6 +86,38 @@ const offsetOn = (day, tz) => {
86
86
  return '+00:00';
87
87
  }
88
88
  };
89
+ /**
90
+ * An instant as the hotel's own clock shows it - "Sat 17 Oct, 15:00".
91
+ *
92
+ * ⚠️ A DEADLINE WITHOUT ITS HOUR IS NOT A DEADLINE. A free-cancellation window
93
+ * that closes at 15:00 was shown as "until 2026-10-17", and a guest cancelling
94
+ * that evening would have been charged a night (2026-10-05 audit). In the
95
+ * hotel's time, because that is the clock the desk will hold them to.
96
+ */
97
+ export const atHotelTime = (instant, tz) => {
98
+ const style = {
99
+ weekday: 'short',
100
+ day: 'numeric',
101
+ month: 'short',
102
+ hour: '2-digit',
103
+ minute: '2-digit',
104
+ hourCycle: 'h23',
105
+ };
106
+ if (isZone(tz)) {
107
+ try {
108
+ return new Intl.DateTimeFormat('en-GB', { ...style, timeZone: tz }).format(instant);
109
+ }
110
+ catch {
111
+ // An unknown zone falls through to UTC, said as UTC.
112
+ }
113
+ }
114
+ const m = /^([+-])(\d{2}):(\d{2})$/.exec(tz);
115
+ const shift = m
116
+ ? (m[1] === '-' ? -1 : 1) * (Number(m[2] ?? 0) * 60 + Number(m[3] ?? 0)) * 60_000
117
+ : 0;
118
+ const shown = new Intl.DateTimeFormat('en-GB', { ...style, timeZone: 'UTC' }).format(instant + shift);
119
+ return m || isZone(tz) ? shown : `${shown} UTC`;
120
+ };
89
121
  /** The moment a guest can walk in, as an instant. */
90
122
  export const arrivalInstant = (checkIn, checkInFrom, tz) => {
91
123
  const time = checkInFrom.length === 5 ? checkInFrom : '15:00';
package/dist/dates.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"dates.js","sourceRoot":"","sources":["../src/dates.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dates.js","sourceRoot":"","sources":["../src/dates.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,8EAA8E;AAC9E,mEAAmE;AAEnE,oFAAoF;AACpF,MAAM,EAAE,GAAG,CAAC,GAAW,EAAU,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC;AAEnE,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC;AAEjC,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAAU,EAAe,EAAE,CAC/C,OAAO,CAAC,KAAK,QAAQ;IACrB,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7B,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACpB,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAErB,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAErF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,CAAS,EAAU,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;AAEvF,qEAAqE;AACrE,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAe,EAAE,QAAgB,EAAU,EAAE,CAClE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;AAEpD,kFAAkF;AAClF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,OAAe,EAAE,QAAgB,EAAY,EAAE;IACvE,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IAC/C,KAAK,IAAI,CAAC,GAAG,OAAO,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrE,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,oEAAoE;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAW,EAAW,EAAE;IACrD,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IAC1C,OAAO,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;AAChC,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,GAAG,CAAC,EAAU,EAAW,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAEzD,oFAAoF;AACpF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,EAAU,EAAU,EAAE;IACzD,IAAI,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;QACf,IAAI,CAAC;YACH,wEAAwE;YACxE,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;gBACtC,QAAQ,EAAE,EAAE;gBACZ,IAAI,EAAE,SAAS;gBACf,KAAK,EAAE,SAAS;gBAChB,GAAG,EAAE,SAAS;aACf,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;YACzE,4DAA4D;YAC5D,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,MAAM,CAAC,GAAG,yBAAyB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,CAAC;QACb,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM;QACjF,CAAC,CAAC,CAAC,CAAC;IACN,OAAO,IAAI,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1D,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAE,EAAU,EAAU,EAAE;IACnD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;YAC7C,QAAQ,EAAE,EAAE;YACZ,YAAY,EAAE,YAAY;YAC1B,IAAI,EAAE,SAAS;SAChB,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,cAAc,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;QACvE,MAAM,CAAC,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAe,EAAE,EAAU,EAAU,EAAE;IACjE,MAAM,KAAK,GAA+B;QACxC,OAAO,EAAE,OAAO;QAChB,GAAG,EAAE,SAAS;QACd,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,SAAS;QACjB,SAAS,EAAE,KAAK;KACjB,CAAC;IACF,IAAI,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;QACf,IAAI,CAAC;YACH,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACtF,CAAC;QAAC,MAAM,CAAC;YACP,qDAAqD;QACvD,CAAC;IACH,CAAC;IACD,MAAM,CAAC,GAAG,yBAAyB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,CAAC;QACb,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM;QACjF,CAAC,CAAC,CAAC,CAAC;IACN,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAClF,OAAO,GAAG,KAAK,CAChB,CAAC;IACF,OAAO,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC;AAClD,CAAC,CAAC;AAEF,qDAAqD;AACrD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAe,EAAE,WAAmB,EAAE,EAAU,EAAU,EAAE;IACzF,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC;IAC9D,8EAA8E;IAC9E,oEAAoE;IACpE,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,IAAI,MAAM,MAAM,EAAE,CAAC,CAAC;AACtD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,uFAAuF;AACvF,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,IAAiE,EACjE,KAAa,EACE,EAAE;IACjB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,0CAA0C,CAAC;IAC5E,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,2CAA2C,CAAC;IAC9E,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,IAAI,CAAC;QACR,OAAO,gFAAgF,CAAC;IAC1F,IAAI,CAAC,GAAG,UAAU;QAChB,OAAO,+BAA+B,UAAU,mCAAmC,CAAC,GAAG,CAAC;IAC1F,IAAI,IAAI,CAAC,OAAO,GAAG,KAAK;QAAE,OAAO,YAAY,IAAI,CAAC,OAAO,8BAA8B,KAAK,GAAG,CAAC;IAChG,IAAI,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,cAAc;QAC9C,OAAO,yBAAyB,cAAc,gBAAgB,IAAI,CAAC,OAAO,kBAAkB,CAAC;IAC/F,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC;QACvE,OAAO,yDAAyD,CAAC;IACnE,IAAI,MAAM,GAAG,UAAU;QACrB,OAAO,aAAa,UAAU,+CAA+C,CAAC;IAChF,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import type { HotelBackend } from './backend.js';
2
+ import type { StateFile } from './state.js';
2
3
  import type { Booking, ExtraKind, HotelPolicyTopic, TripLine } from './types.js';
3
4
  /**
4
5
  * The hotels, out of a JSON file, priced by us.
@@ -112,7 +113,7 @@ export type RawHotelData = {
112
113
  membership?: RawMembership;
113
114
  };
114
115
  export type DemoError = Error & {
115
- code: 'not-found' | 'bad-stay' | 'unavailable' | 'mixed-currency' | 'empty-trip' | 'no-guest' | 'partly-unavailable' | 'too-many-holds';
116
+ code: 'not-found' | 'bad-stay' | 'unavailable' | 'mixed-currency' | 'empty-trip' | 'no-guest' | 'partly-unavailable' | 'too-many-holds' | 'demo-busy';
116
117
  };
117
118
  export declare const demoError: (code: DemoError["code"], message: string) => DemoError;
118
119
  export type DemoHotelsOptions = {
@@ -122,6 +123,10 @@ export type DemoHotelsOptions = {
122
123
  /** Injected so tests do not depend on the day they run. */
123
124
  now?: () => number;
124
125
  rng?: () => number;
126
+ /** Where bookings and saved preferences survive a restart - none, and they stay in memory. */
127
+ state?: StateFile;
128
+ /** Room-nights all live holds together may take - see MAX_HELD_ROOM_NIGHTS. */
129
+ maxHeldRoomNights?: number;
125
130
  };
126
131
  /**
127
132
  * A booking as this file keeps it: the booking, and the conversations that may
@@ -148,6 +153,21 @@ type StoredBooking = Booking & {
148
153
  * for the two hours the holds last.
149
154
  */
150
155
  export declare const MAX_HOLDS_PER_CONVERSATION = 10;
156
+ /**
157
+ * Room-nights every live hold together may take, across all conversations.
158
+ *
159
+ * ⚠️ THE CAP ABOVE IS PER CONVERSATION, AND CONVERSATIONS ARE FREE. Twelve
160
+ * made-up session ids, one booking each, emptied the Island Shangri-La for a
161
+ * month (2026-10-05 audit) - and a session id can be had from `initialize` for
162
+ * the asking, so checking that the id is ours raises the price of the attack
163
+ * from nothing to one request. What bounds the damage is this: past it, a hold
164
+ * is refused for everybody until some lapse, which is the demo saying "busy"
165
+ * rather than every room in a city saying "taken". Far above what guests and a
166
+ * full eval round hold together (a few dozen).
167
+ */
168
+ export declare const MAX_HELD_ROOM_NIGHTS = 400;
169
+ /** A booking is kept this long after its check-out date, then forgotten - the privacy page says so. */
170
+ export declare const KEEP_AFTER_CHECKOUT_DAYS = 90;
151
171
  /**
152
172
  * A trip cut into the stays a hotel would confirm one by one: the rooms at one
153
173
  * hotel for the same nights, and the add-ons bought for those nights.
@@ -1 +1 @@
1
- {"version":3,"file":"demo-hotels.d.ts","sourceRoot":"","sources":["../src/demo-hotels.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"demo-hotels.d.ts","sourceRoot":"","sources":["../src/demo-hotels.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { randomInt, randomUUID } from 'node:crypto';
2
2
  import { DEFAULT_PAGE, LASTING_REQUESTS, MAX_PAGE, REQUEST_KINDS, REQUEST_NOTE_MAX, } from './backend.js';
3
- import { addDays, arrivalInstant, eachNight, isWeekendNight, nights as nightsBetween, stayProblem, todayAt, } from './dates.js';
3
+ import { addDays, arrivalInstant, atHotelTime, eachNight, isWeekendNight, nights as nightsBetween, stayProblem, todayAt, } from './dates.js';
4
4
  import { FACILITIES, hasFacility } from './facilities.js';
5
5
  import { addMoney, formatMoney, minorFactor, money, multiplyMoney, taxOn, toMinor, wholeUnits, } from './money.js';
6
6
  import { MAX_EXTRA_QUANTITY, MAX_ROOMS_PER_LINE } from './trip.js';
@@ -46,6 +46,23 @@ const MAX_BOOKINGS = 5_000;
46
46
  * for the two hours the holds last.
47
47
  */
48
48
  export const MAX_HOLDS_PER_CONVERSATION = 10;
49
+ /**
50
+ * Room-nights every live hold together may take, across all conversations.
51
+ *
52
+ * ⚠️ THE CAP ABOVE IS PER CONVERSATION, AND CONVERSATIONS ARE FREE. Twelve
53
+ * made-up session ids, one booking each, emptied the Island Shangri-La for a
54
+ * month (2026-10-05 audit) - and a session id can be had from `initialize` for
55
+ * the asking, so checking that the id is ours raises the price of the attack
56
+ * from nothing to one request. What bounds the damage is this: past it, a hold
57
+ * is refused for everybody until some lapse, which is the demo saying "busy"
58
+ * rather than every room in a city saying "taken". Far above what guests and a
59
+ * full eval round hold together (a few dozen).
60
+ */
61
+ export const MAX_HELD_ROOM_NIGHTS = 400;
62
+ /** A booking is kept this long after its check-out date, then forgotten - the privacy page says so. */
63
+ export const KEEP_AFTER_CHECKOUT_DAYS = 90;
64
+ /** A cancellation reason is a sentence, not a document: it is kept with the booking. */
65
+ const REASON_MAX = 300;
49
66
  const publicBooking = (b) => {
50
67
  const { owners, ...rest } = b;
51
68
  void owners;
@@ -143,6 +160,22 @@ export const createDemoHotels = (opts) => {
143
160
  const bookings = new Map();
144
161
  /** Saved room preferences, by membership number - see `preferencesFor`. */
145
162
  const preferences = new Map();
163
+ const maxHeldRoomNights = opts.maxHeldRoomNights ?? MAX_HELD_ROOM_NIGHTS;
164
+ const saved = opts.state?.read();
165
+ for (const b of saved?.bookings ?? []) {
166
+ if (typeof b?.id !== 'string' || typeof b.confirmationCode !== 'string')
167
+ continue;
168
+ bookings.set(b.id, { ...b, owners: new Set(Array.isArray(b.owners) ? b.owners : []) });
169
+ }
170
+ for (const [number, kinds] of saved?.preferences ?? []) {
171
+ if (typeof number === 'string' && Array.isArray(kinds))
172
+ preferences.set(number, kinds);
173
+ }
174
+ opts.state?.bind(() => ({
175
+ bookings: [...bookings.values()].map((b) => ({ ...b, owners: [...b.owners] })),
176
+ preferences: [...preferences],
177
+ }));
178
+ const changed = () => opts.state?.changed();
146
179
  const byId = (id) => {
147
180
  const wanted = id.trim().toLowerCase();
148
181
  return (hotels.find((h) => h.id.toLowerCase() === wanted) ??
@@ -244,14 +277,35 @@ export const createDemoHotels = (opts) => {
244
277
  }
245
278
  return Math.max(...nights.values());
246
279
  };
247
- /** Requests that were never taken up stop holding the room. */
280
+ /**
281
+ * Requests that were never taken up stop holding the room, and a booking
282
+ * whose stay ended more than KEEP_AFTER_CHECKOUT_DAYS ago is forgotten.
283
+ */
248
284
  const sweep = () => {
249
285
  const t = now();
250
- for (const b of bookings.values()) {
251
- if (b.status === 'requested' && b.holdUntil && Date.parse(b.holdUntil) <= t)
286
+ const cutoff = new Date(t - KEEP_AFTER_CHECKOUT_DAYS * 86_400_000).toISOString().slice(0, 10);
287
+ let dirty = false;
288
+ for (const b of [...bookings.values()]) {
289
+ if (b.stay.checkOut < cutoff) {
290
+ bookings.delete(b.id);
291
+ dirty = true;
292
+ }
293
+ else if (b.status === 'requested' && b.holdUntil && Date.parse(b.holdUntil) <= t) {
252
294
  b.status = 'expired';
295
+ dirty = true;
296
+ }
253
297
  }
298
+ if (dirty)
299
+ changed();
254
300
  };
301
+ /** Room-nights a booking holds - rooms times nights, line by line. */
302
+ const roomNightsOf = (quote, stay) => quote.lines.reduce((sum, line) => {
303
+ if (line.kind !== 'room')
304
+ return sum;
305
+ const held = line.stay ?? stay ?? quote.stay;
306
+ const nights = held ? eachNight(held.checkIn, held.checkOut).length : 0;
307
+ return sum + nights * (line.quantity ?? 1);
308
+ }, 0);
255
309
  /**
256
310
  * Until when this room on these nights can be cancelled for nothing - or
257
311
  * nothing, when it cannot.
@@ -311,6 +365,9 @@ export const createDemoHotels = (opts) => {
311
365
  nights: perNight.length,
312
366
  refundable: r.refundable,
313
367
  freeCancellationUntil: freeUntil,
368
+ ...(freeUntil
369
+ ? { freeCancellationLocal: atHotelTime(Date.parse(freeUntil), offsetOf(h)) }
370
+ : {}),
314
371
  roomsLeft: Math.max(0, r.roomsLeft - heldOn(h.id, r.id, stay, ignore)),
315
372
  };
316
373
  };
@@ -414,7 +471,7 @@ export const createDemoHotels = (opts) => {
414
471
  return own;
415
472
  const email = who.email.trim().toLowerCase();
416
473
  const there = [...bookings.values()].find((x) => x.confirmationCode.toLowerCase() === wanted && x.guest.email.trim().toLowerCase() === email);
417
- if (there) {
474
+ if (there && !there.owners.has(who.sessionId)) {
418
475
  there.owners.add(who.sessionId);
419
476
  while (there.owners.size > MAX_OWNERS) {
420
477
  const [, second] = [...there.owners];
@@ -423,6 +480,7 @@ export const createDemoHotels = (opts) => {
423
480
  break;
424
481
  there.owners.delete(second);
425
482
  }
483
+ changed();
426
484
  }
427
485
  return there;
428
486
  };
@@ -433,8 +491,10 @@ export const createDemoHotels = (opts) => {
433
491
  for (const b of [...bookings.values()]) {
434
492
  if (bookings.size + incoming <= MAX_BOOKINGS)
435
493
  return;
436
- if (ended(b))
494
+ if (ended(b)) {
437
495
  bookings.delete(b.id);
496
+ changed();
497
+ }
438
498
  }
439
499
  };
440
500
  /** A room type by its id or the name the hotel prints. */
@@ -882,7 +942,7 @@ export const createDemoHotels = (opts) => {
882
942
  if (line.freeCancellationUntil && at <= Date.parse(line.freeCancellationUntil)) {
883
943
  if (!freeUntil || line.freeCancellationUntil < freeUntil)
884
944
  freeUntil = line.freeCancellationUntil;
885
- rules.push(`${line.title} is free to cancel until ${line.freeCancellationUntil.slice(0, 16).replace('T', ' ')} UTC.`);
945
+ rules.push(`${line.title} is free to cancel until ${h ? `${atHotelTime(Date.parse(line.freeCancellationUntil), offsetOf(h))} hotel time` : `${line.freeCancellationUntil.slice(0, 16).replace('T', ' ')} UTC`}.`);
886
946
  continue;
887
947
  }
888
948
  // A night at the booking's own average nightly rate - a stay of a
@@ -1269,8 +1329,8 @@ export const createDemoHotels = (opts) => {
1269
1329
  const member = memberFor(number, surname);
1270
1330
  if (!member)
1271
1331
  return null;
1272
- // In memory, like everything else in the demo: kept until this process
1273
- // restarts. The real programme keeps them on the member's profile.
1332
+ // Kept with the bookings (on disk when the deployment has a state
1333
+ // directory). The real programme keeps them on the member's profile.
1274
1334
  const saved = preferences.get(member.number.toUpperCase());
1275
1335
  return saved?.length ? { ...member, preferences: [...saved] } : member;
1276
1336
  },
@@ -1280,6 +1340,7 @@ export const createDemoHotels = (opts) => {
1280
1340
  preferences.set(member.number.toUpperCase(), kept);
1281
1341
  else
1282
1342
  preferences.delete(member.number.toUpperCase());
1343
+ changed();
1283
1344
  return kept;
1284
1345
  },
1285
1346
  async requestBooking(trip, guest, who, requests) {
@@ -1327,6 +1388,13 @@ export const createDemoHotels = (opts) => {
1327
1388
  throw demoError('too-many-holds', `Nothing was booked. This conversation already holds ${live} booking${live === 1 ? '' : 's'}, and ${MAX_HOLDS_PER_CONVERSATION} at once is the most - cancel one that is not needed, then ask again.`);
1328
1389
  }
1329
1390
  const quotes = await Promise.all(groups.map((g) => quoteTrip({ lines: g }, who)));
1391
+ const held = [...bookings.values()]
1392
+ .filter((b) => b.status === 'requested')
1393
+ .reduce((sum, b) => sum + roomNightsOf(b.quote, b.stay), 0);
1394
+ const asked = quotes.reduce((sum, q) => sum + roomNightsOf(q), 0);
1395
+ if (held + asked > maxHeldRoomNights) {
1396
+ throw demoError('demo-busy', 'Nothing was booked. This demonstration holds only so many rooms at once, across everybody using it, and that many are held right now; holds lapse on their own within a couple of hours. The trip is as it was - try again later.');
1397
+ }
1330
1398
  const t = now();
1331
1399
  const made = [];
1332
1400
  for (const quote of quotes) {
@@ -1357,6 +1425,7 @@ export const createDemoHotels = (opts) => {
1357
1425
  makeRoom(made.length);
1358
1426
  for (const booking of made)
1359
1427
  bookings.set(booking.id, booking);
1428
+ changed();
1360
1429
  return made.map(publicBooking);
1361
1430
  },
1362
1431
  async listBookings(who, limit = 10) {
@@ -1406,6 +1475,7 @@ export const createDemoHotels = (opts) => {
1406
1475
  changedAt: new Date(now()).toISOString(),
1407
1476
  };
1408
1477
  bookings.set(b.id, updated);
1478
+ changed();
1409
1479
  return publicBooking(updated);
1410
1480
  },
1411
1481
  async previewCancellation(bookingRef, who) {
@@ -1444,11 +1514,13 @@ export const createDemoHotels = (opts) => {
1444
1514
  b.status = 'cancelled';
1445
1515
  b.cancelledAt = new Date(now()).toISOString();
1446
1516
  b.cancellationFee = fee;
1447
- if (reason)
1517
+ const why = reason?.replace(/\s+/g, ' ').trim().slice(0, REASON_MAX);
1518
+ if (why)
1448
1519
  b.guest = {
1449
1520
  ...b.guest,
1450
- notes: [b.guest.notes, `Cancelled: ${reason}`].filter(Boolean).join(' | '),
1521
+ notes: [b.guest.notes, `Cancelled: ${why}`].filter(Boolean).join(' | '),
1451
1522
  };
1523
+ changed();
1452
1524
  return publicBooking(b);
1453
1525
  },
1454
1526
  async policies(hotelId) {