@indietabletop/appkit 6.1.6 → 7.0.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.
Files changed (269) hide show
  1. package/dist/AppConfig/AppConfig.d.ts +29 -0
  2. package/dist/AuthCard/AuthCard.d.ts +10 -0
  3. package/dist/AuthCard/AuthCard.stories.d.ts +34 -0
  4. package/dist/AuthCard/style.css.d.ts +23 -0
  5. package/dist/DialogTrigger/index.d.ts +13 -0
  6. package/dist/DocumentTitle/DocumentTitle.d.ts +3 -0
  7. package/dist/EnumMapper.d.ts +25 -0
  8. package/dist/ExternalLink.d.ts +3 -0
  9. package/dist/FullscreenDismissBlocker.d.ts +5 -0
  10. package/{lib/HistoryState.ts → dist/HistoryState.d.ts} +2 -5
  11. package/dist/IndieTabletopClubLogo.d.ts +7 -0
  12. package/dist/IndieTabletopClubSymbol.d.ts +7 -0
  13. package/dist/InfoPage/index.d.ts +8 -0
  14. package/dist/InfoPage/pages.d.ts +2 -0
  15. package/dist/InfoPage/style.css.d.ts +5 -0
  16. package/dist/Letterhead/index.d.ts +19 -0
  17. package/dist/Letterhead/stories.d.ts +13 -0
  18. package/dist/Letterhead/style.css.d.ts +46 -0
  19. package/dist/LetterheadForm/LetterheadReadonlyTextField.stories.d.ts +17 -0
  20. package/dist/LetterheadForm/LetterheadSubmitError.stories.d.ts +11 -0
  21. package/dist/LetterheadForm/LetterheadTextField.stories.d.ts +336 -0
  22. package/dist/LetterheadForm/index.d.ts +44 -0
  23. package/dist/LetterheadForm/style.css.d.ts +8 -0
  24. package/dist/LoadingIndicator.d.ts +3 -0
  25. package/dist/MiddotSeparated/MiddotSeparated.d.ts +8 -0
  26. package/dist/MiddotSeparated/MiddotSeparated.stories.d.ts +586 -0
  27. package/dist/MiddotSeparated/style.css.d.ts +1 -0
  28. package/dist/ModalDialog/index.d.ts +12 -0
  29. package/dist/ModalDialog/style.css.d.ts +58 -0
  30. package/dist/ModernIDB/Cursor.d.ts +56 -0
  31. package/dist/ModernIDB/ModernIDB.d.ts +66 -0
  32. package/dist/ModernIDB/ModernIDBError.d.ts +3 -0
  33. package/dist/ModernIDB/ObjectStore.d.ts +112 -0
  34. package/dist/ModernIDB/ObjectStoreIndex.d.ts +53 -0
  35. package/dist/ModernIDB/Transaction.d.ts +16 -0
  36. package/dist/ModernIDB/VersionChangeManager.d.ts +30 -0
  37. package/dist/ModernIDB/bindings/factory.d.ts +12 -0
  38. package/dist/ModernIDB/bindings/index.d.ts +2 -0
  39. package/{lib/ModernIDB/bindings/types.ts → dist/ModernIDB/bindings/types.d.ts} +13 -32
  40. package/dist/ModernIDB/bindings/utils.d.ts +2 -0
  41. package/dist/ModernIDB/index.d.ts +10 -0
  42. package/dist/ModernIDB/types.d.ts +88 -0
  43. package/dist/ModernIDB/utils.d.ts +4 -0
  44. package/dist/QRCode/QRCode.d.ts +7 -0
  45. package/dist/QRCode/QRCode.stories.d.ts +33 -0
  46. package/dist/QRCode/style.css.d.ts +4 -0
  47. package/dist/ReleaseInfo/index.d.ts +5 -0
  48. package/dist/RulesetResolver.d.ts +87 -0
  49. package/dist/SafariCheck/SafariCheck.d.ts +23 -0
  50. package/dist/SafariCheck/SafariCheck.stories.d.ts +73 -0
  51. package/dist/SafariCheck/style.css.d.ts +17 -0
  52. package/dist/ServiceWorkerHandler.d.ts +11 -0
  53. package/dist/ShareButton/ShareButton.d.ts +57 -0
  54. package/dist/ShareButton/ShareButton.stories.d.ts +1577 -0
  55. package/dist/ShareButton/test.css.d.ts +1 -0
  56. package/dist/SubscribeCard/LetterheadInfoCard.d.ts +2 -0
  57. package/dist/SubscribeCard/SubscribeByEmailCard.d.ts +24 -0
  58. package/dist/SubscribeCard/SubscribeByEmailCard.stories.d.ts +10 -0
  59. package/dist/SubscribeCard/SubscribeByPledgeCard.d.ts +36 -0
  60. package/dist/SubscribeCard/SubscribeByPledgeCard.stories.d.ts +65 -0
  61. package/dist/SubscribeCard/style.css.d.ts +4 -0
  62. package/dist/account/AccountIssueView.d.ts +3 -0
  63. package/dist/account/AlreadyLoggedInView.d.ts +5 -0
  64. package/dist/account/CurrentUserFetcher.d.ts +20 -0
  65. package/dist/account/CurrentUserFetcher.stories.d.ts +136 -0
  66. package/dist/account/FailureFallbackView.d.ts +1 -0
  67. package/dist/account/JoinCard.d.ts +14 -0
  68. package/dist/account/JoinCard.stories.d.ts +143 -0
  69. package/dist/account/LoadingView.d.ts +1 -0
  70. package/dist/account/LoginCard.d.ts +39 -0
  71. package/dist/account/LoginCard.stories.d.ts +217 -0
  72. package/dist/account/LoginView.d.ts +10 -0
  73. package/dist/account/NoConnectionView.d.ts +4 -0
  74. package/dist/account/PasswordResetCard.d.ts +15 -0
  75. package/dist/account/PasswordResetCard.stories.d.ts +128 -0
  76. package/dist/account/UserMismatchView.d.ts +6 -0
  77. package/dist/account/VerifyPage.d.ts +13 -0
  78. package/dist/account/style.css.d.ts +10 -0
  79. package/{lib/account/types.ts → dist/account/types.d.ts} +3 -6
  80. package/dist/account/useFetchCurrentUser.d.ts +28 -0
  81. package/dist/account/useRedirectPath.d.ts +6 -0
  82. package/dist/animations.css.d.ts +3 -0
  83. package/dist/append-copy-to-text.d.ts +10 -0
  84. package/dist/append-copy-to-text.test.d.ts +1 -0
  85. package/dist/appkit.css +1 -0
  86. package/dist/appkit.js +10692 -0
  87. package/dist/async-op.d.ts +101 -0
  88. package/dist/atomic.css.d.ts +6 -0
  89. package/{lib/caught-value.ts → dist/caught-value.d.ts} +1 -11
  90. package/{lib/class-names.ts → dist/class-names.d.ts} +6 -17
  91. package/dist/client.d.ts +424 -0
  92. package/dist/common.css.d.ts +5 -0
  93. package/dist/copyrightRange.d.ts +1 -0
  94. package/dist/copyrightRange.test.d.ts +1 -0
  95. package/dist/createSafeStorage.d.ts +34 -0
  96. package/dist/failureMessages.d.ts +20 -0
  97. package/dist/failureMessages.test.d.ts +1 -0
  98. package/dist/form/FormSubmitButton.d.ts +17 -0
  99. package/dist/form/SubmitErrorAlert.d.ts +5 -0
  100. package/dist/form/style.css.d.ts +3 -0
  101. package/dist/globals.css.d.ts +0 -0
  102. package/dist/groupBy.d.ts +1 -0
  103. package/dist/groupBy.test.d.ts +1 -0
  104. package/dist/hrefs.d.ts +32 -0
  105. package/dist/hrefs.test.d.ts +1 -0
  106. package/dist/idToDate.d.ts +5 -0
  107. package/dist/idToDate.test.d.ts +1 -0
  108. package/dist/ids.d.ts +1 -0
  109. package/dist/ids.test.d.ts +1 -0
  110. package/dist/index.d.ts +64 -0
  111. package/dist/internal.css.d.ts +2 -0
  112. package/dist/mailto.d.ts +8 -0
  113. package/dist/mailto.test.d.ts +1 -0
  114. package/dist/media.d.ts +39 -0
  115. package/dist/random.d.ts +3 -0
  116. package/dist/result/swr.d.ts +4 -0
  117. package/{lib/sleep.ts → dist/sleep.d.ts} +1 -3
  118. package/dist/store/index.d.ts +237 -0
  119. package/dist/store/store.d.ts +144 -0
  120. package/dist/store/types.d.ts +49 -0
  121. package/dist/store/utils.d.ts +10 -0
  122. package/dist/storybook/decorators.d.ts +3 -0
  123. package/dist/structs.d.ts +1 -0
  124. package/{lib/typeguards.ts → dist/typeguards.d.ts} +1 -3
  125. package/dist/typeguards.test.d.ts +1 -0
  126. package/{lib/types.ts → dist/types.d.ts} +12 -23
  127. package/dist/unique.d.ts +10 -0
  128. package/dist/unique.test.d.ts +1 -0
  129. package/dist/use-async-op.d.ts +6 -0
  130. package/dist/use-document-background-color.d.ts +4 -0
  131. package/dist/use-form.d.ts +29 -0
  132. package/dist/use-is-installed.d.ts +8 -0
  133. package/dist/use-media-query.d.ts +1 -0
  134. package/dist/use-reverting-state.d.ts +5 -0
  135. package/dist/use-scroll-restoration.d.ts +25 -0
  136. package/dist/useEnsureValue.d.ts +6 -0
  137. package/dist/useInvokeClient.d.ts +25 -0
  138. package/dist/useIsVisible.d.ts +4 -0
  139. package/dist/utm.d.ts +58 -0
  140. package/dist/utm.test.d.ts +1 -0
  141. package/dist/validations.d.ts +3 -0
  142. package/dist/vars.css.d.ts +10 -0
  143. package/package.json +12 -5
  144. package/lib/AppConfig/AppConfig.tsx +0 -61
  145. package/lib/AuthCard/AuthCard.stories.ts +0 -34
  146. package/lib/AuthCard/AuthCard.tsx +0 -64
  147. package/lib/AuthCard/style.css.ts +0 -49
  148. package/lib/DialogTrigger/index.tsx +0 -36
  149. package/lib/DocumentTitle/DocumentTitle.tsx +0 -10
  150. package/lib/EnumMapper.ts +0 -50
  151. package/lib/ExternalLink.tsx +0 -10
  152. package/lib/FullscreenDismissBlocker.tsx +0 -23
  153. package/lib/IndieTabletopClubLogo.tsx +0 -44
  154. package/lib/IndieTabletopClubSymbol.tsx +0 -37
  155. package/lib/InfoPage/index.tsx +0 -46
  156. package/lib/InfoPage/pages.tsx +0 -36
  157. package/lib/InfoPage/style.css.ts +0 -36
  158. package/lib/Letterhead/index.tsx +0 -85
  159. package/lib/Letterhead/stories.tsx +0 -41
  160. package/lib/Letterhead/style.css.ts +0 -152
  161. package/lib/LetterheadForm/LetterheadReadonlyTextField.stories.tsx +0 -17
  162. package/lib/LetterheadForm/LetterheadSubmitError.stories.tsx +0 -19
  163. package/lib/LetterheadForm/LetterheadTextField.stories.tsx +0 -19
  164. package/lib/LetterheadForm/index.tsx +0 -137
  165. package/lib/LetterheadForm/style.css.ts +0 -89
  166. package/lib/LoadingIndicator.tsx +0 -40
  167. package/lib/MiddotSeparated/MiddotSeparated.stories.ts +0 -26
  168. package/lib/MiddotSeparated/MiddotSeparated.tsx +0 -26
  169. package/lib/MiddotSeparated/style.css.ts +0 -10
  170. package/lib/ModalDialog/index.tsx +0 -28
  171. package/lib/ModalDialog/style.css.ts +0 -88
  172. package/lib/ModernIDB/Cursor.ts +0 -91
  173. package/lib/ModernIDB/ModernIDB.ts +0 -337
  174. package/lib/ModernIDB/ModernIDBError.ts +0 -9
  175. package/lib/ModernIDB/ObjectStore.ts +0 -195
  176. package/lib/ModernIDB/ObjectStoreIndex.ts +0 -102
  177. package/lib/ModernIDB/README.md +0 -9
  178. package/lib/ModernIDB/Transaction.ts +0 -40
  179. package/lib/ModernIDB/VersionChangeManager.ts +0 -57
  180. package/lib/ModernIDB/bindings/factory.tsx +0 -165
  181. package/lib/ModernIDB/bindings/index.ts +0 -2
  182. package/lib/ModernIDB/bindings/utils.tsx +0 -32
  183. package/lib/ModernIDB/index.ts +0 -10
  184. package/lib/ModernIDB/types.ts +0 -120
  185. package/lib/ModernIDB/utils.ts +0 -51
  186. package/lib/QRCode/QRCode.stories.tsx +0 -41
  187. package/lib/QRCode/QRCode.tsx +0 -54
  188. package/lib/QRCode/style.css.ts +0 -23
  189. package/lib/ReleaseInfo/index.tsx +0 -29
  190. package/lib/RulesetResolver.ts +0 -214
  191. package/lib/SafariCheck/SafariCheck.stories.tsx +0 -99
  192. package/lib/SafariCheck/SafariCheck.tsx +0 -273
  193. package/lib/SafariCheck/addToDock.svg +0 -13
  194. package/lib/SafariCheck/addToHomeScreen.svg +0 -12
  195. package/lib/SafariCheck/safari.svg +0 -32
  196. package/lib/SafariCheck/shareIcon.svg +0 -11
  197. package/lib/SafariCheck/style.css.ts +0 -106
  198. package/lib/ServiceWorkerHandler.tsx +0 -53
  199. package/lib/ShareButton/ShareButton.stories.tsx +0 -58
  200. package/lib/ShareButton/ShareButton.tsx +0 -153
  201. package/lib/ShareButton/test.css.ts +0 -3
  202. package/lib/SubscribeCard/LetterheadInfoCard.tsx +0 -23
  203. package/lib/SubscribeCard/SubscribeByEmailCard.stories.tsx +0 -69
  204. package/lib/SubscribeCard/SubscribeByEmailCard.tsx +0 -183
  205. package/lib/SubscribeCard/SubscribeByPledgeCard.stories.tsx +0 -133
  206. package/lib/SubscribeCard/SubscribeByPledgeCard.tsx +0 -127
  207. package/lib/SubscribeCard/style.css.ts +0 -14
  208. package/lib/account/AccountIssueView.tsx +0 -44
  209. package/lib/account/AlreadyLoggedInView.tsx +0 -47
  210. package/lib/account/CurrentUserFetcher.stories.tsx +0 -292
  211. package/lib/account/CurrentUserFetcher.tsx +0 -118
  212. package/lib/account/FailureFallbackView.tsx +0 -36
  213. package/lib/account/JoinCard.stories.tsx +0 -257
  214. package/lib/account/JoinCard.tsx +0 -301
  215. package/lib/account/LoadingView.tsx +0 -14
  216. package/lib/account/LoginCard.stories.tsx +0 -288
  217. package/lib/account/LoginCard.tsx +0 -100
  218. package/lib/account/LoginView.tsx +0 -151
  219. package/lib/account/NoConnectionView.tsx +0 -34
  220. package/lib/account/PasswordResetCard.stories.tsx +0 -242
  221. package/lib/account/PasswordResetCard.tsx +0 -296
  222. package/lib/account/UserMismatchView.tsx +0 -62
  223. package/lib/account/VerifyPage.tsx +0 -195
  224. package/lib/account/style.css.ts +0 -57
  225. package/lib/account/useFetchCurrentUser.tsx +0 -63
  226. package/lib/account/useRedirectPath.ts +0 -21
  227. package/lib/animations.css.ts +0 -17
  228. package/lib/append-copy-to-text.ts +0 -35
  229. package/lib/async-op.ts +0 -286
  230. package/lib/atomic.css.ts +0 -11
  231. package/lib/client.ts +0 -662
  232. package/lib/common.css.ts +0 -48
  233. package/lib/copyrightRange.ts +0 -10
  234. package/lib/createSafeStorage.ts +0 -91
  235. package/lib/failureMessages.ts +0 -108
  236. package/lib/form/FormSubmitButton.tsx +0 -58
  237. package/lib/form/SubmitErrorAlert.tsx +0 -21
  238. package/lib/form/style.css.ts +0 -9
  239. package/lib/globals.css.ts +0 -62
  240. package/lib/groupBy.ts +0 -25
  241. package/lib/hrefs.ts +0 -48
  242. package/lib/idToDate.ts +0 -8
  243. package/lib/ids.ts +0 -6
  244. package/lib/index.ts +0 -71
  245. package/lib/internal.css.ts +0 -10
  246. package/lib/mailto.ts +0 -40
  247. package/lib/media.ts +0 -50
  248. package/lib/random.ts +0 -19
  249. package/lib/result/swr.ts +0 -18
  250. package/lib/store/index.tsx +0 -241
  251. package/lib/store/store.ts +0 -479
  252. package/lib/store/types.ts +0 -45
  253. package/lib/store/utils.ts +0 -54
  254. package/lib/storybook/decorators.tsx +0 -10
  255. package/lib/structs.ts +0 -3
  256. package/lib/unique.ts +0 -24
  257. package/lib/use-async-op.ts +0 -16
  258. package/lib/use-document-background-color.ts +0 -16
  259. package/lib/use-form.ts +0 -78
  260. package/lib/use-is-installed.ts +0 -17
  261. package/lib/use-media-query.ts +0 -21
  262. package/lib/use-reverting-state.ts +0 -32
  263. package/lib/use-scroll-restoration.ts +0 -99
  264. package/lib/useEnsureValue.ts +0 -31
  265. package/lib/useInvokeClient.ts +0 -54
  266. package/lib/useIsVisible.ts +0 -27
  267. package/lib/utm.ts +0 -92
  268. package/lib/validations.ts +0 -25
  269. package/lib/vars.css.ts +0 -13
package/lib/client.ts DELETED
@@ -1,662 +0,0 @@
1
- import {
2
- currentUser,
3
- featureUnlock,
4
- ownedProduct,
5
- redeemedPledge,
6
- sessionInfo,
7
- type FeatureUnlock,
8
- type UserGameData,
9
- } from "@indietabletop/types";
10
- import {
11
- array,
12
- mask,
13
- object,
14
- partial,
15
- string,
16
- Struct,
17
- unknown,
18
- type Infer,
19
- } from "superstruct";
20
- import { Failure, Success } from "./async-op.ts";
21
- import type { CurrentUser, FailurePayload, SessionInfo } from "./types.ts";
22
-
23
- export type GameCode = keyof UserGameData;
24
-
25
- export type ClientEventType = keyof ClientEventMap;
26
-
27
- type ClientEventMap = {
28
- /**
29
- * Triggered every time currentUser is received.
30
- */
31
- currentUser: { currentUser: CurrentUser };
32
-
33
- /**
34
- * Triggered when new session info is received.
35
- */
36
- sessionInfo: { sessionInfo: SessionInfo };
37
-
38
- /**
39
- * Triggered when token refresh fails due to a 401 error.
40
- */
41
- sessionExpired: undefined;
42
- };
43
-
44
- type ClientEventArgs<T extends ClientEventType> =
45
- ClientEventMap[T] extends undefined
46
- ? [type: T]
47
- : [type: T, detail: ClientEventMap[T]];
48
-
49
- export class ClientEvent<T extends keyof ClientEventMap> extends CustomEvent<
50
- ClientEventMap[T]
51
- > {
52
- constructor(...args: ClientEventArgs<T>) {
53
- const [type, detail] = args;
54
- super(type, { detail });
55
- }
56
- }
57
-
58
- const logLevelToInt = {
59
- off: 0,
60
- error: 1,
61
- warn: 2,
62
- info: 3,
63
- };
64
-
65
- type LogLevel = keyof typeof logLevelToInt;
66
-
67
- type Primitives = string | boolean | number;
68
-
69
- function toParams(init: Record<string, Primitives | Array<Primitives>>) {
70
- const params = new URLSearchParams();
71
-
72
- const entries = Object.entries(init).flatMap(([key, value]) => {
73
- return Array.isArray(value)
74
- ? value.map((v) => [key, v] as const)
75
- : [[key, value] as const];
76
- });
77
-
78
- for (const [key, value] of entries) {
79
- params.append(key, value.toString());
80
- }
81
-
82
- return params;
83
- }
84
-
85
- export class IndieTabletopClient {
86
- origin: string;
87
- private refreshTokenPromise?: Promise<
88
- Success<{ sessionInfo: SessionInfo }> | Failure<FailurePayload>
89
- >;
90
- private maxLogLevel: number;
91
- private eventTarget: EventTarget;
92
-
93
- constructor(props: {
94
- apiOrigin: string;
95
-
96
- /**
97
- * Runs every time the current user is fetched from the API. Typically, this
98
- * happens during login, signup, and when the current user is fetched.
99
- */
100
- onCurrentUser?: (currentUser: CurrentUser) => void;
101
-
102
- /**
103
- * Runs ever time new session info is fetched from the API. Typically, this
104
- * happends during login, signup, and when tokens are refreshed.
105
- */
106
- onSessionInfo?: (sessionInfo: SessionInfo) => void;
107
-
108
- /**
109
- * Runs when token refresh is attempted, but fails due to 401 error.
110
- */
111
- onSessionExpired?: () => void;
112
-
113
- /**
114
- * Controls how much to log to the console.
115
- *
116
- * This is useful e.g. in Storybook, where errors are reported by MSW, and
117
- * we don't want to pollute the console with duplicate data.
118
- *
119
- * @default 'info'
120
- */
121
- logLevel?: LogLevel;
122
- }) {
123
- this.eventTarget = new EventTarget();
124
- this.origin = props.apiOrigin;
125
- this.maxLogLevel = props.logLevel ? logLevelToInt[props.logLevel] : 1;
126
-
127
- // If handlers were passed to the constructor, we set them up here. No need
128
- // to clean them up, as if the instance is destroyed, the listeners will
129
- // go with it.
130
- const { onCurrentUser, onSessionInfo, onSessionExpired } = props;
131
- if (onCurrentUser) {
132
- this.addEventListener("currentUser", (event) => {
133
- return onCurrentUser(event.detail.currentUser);
134
- });
135
- }
136
- if (onSessionInfo) {
137
- this.addEventListener("sessionInfo", (event) => {
138
- return onSessionInfo(event.detail.sessionInfo);
139
- });
140
- }
141
- if (onSessionExpired) {
142
- this.addEventListener("sessionExpired", () => {
143
- return onSessionExpired();
144
- });
145
- }
146
- }
147
-
148
- private dispatchEvent<T extends ClientEventType>(
149
- ...args: ClientEventArgs<T>
150
- ) {
151
- this.eventTarget.dispatchEvent(new ClientEvent(...(args as [any, any])));
152
- }
153
-
154
- public addEventListener<T extends ClientEventType>(
155
- type: T,
156
- callback: (event: ClientEvent<T>) => void,
157
- options?: boolean | AddEventListenerOptions,
158
- ): void {
159
- this.eventTarget.addEventListener(type, callback as EventListener, options);
160
- }
161
-
162
- public removeEventListener<T extends ClientEventType>(
163
- type: T,
164
- callback: (event: ClientEvent<T>) => void,
165
- options?: boolean | AddEventListenerOptions,
166
- ): void {
167
- this.eventTarget.removeEventListener(
168
- type,
169
- callback as EventListener,
170
- options,
171
- );
172
- }
173
-
174
- private log(level: Exclude<LogLevel, "off">, ...messages: unknown[]) {
175
- if (logLevelToInt[level] <= this.maxLogLevel) {
176
- console[level](...messages);
177
- }
178
- }
179
-
180
- private async fetchWithTokenRefresh(...params: Parameters<typeof fetch>) {
181
- const response = await fetch(...params);
182
-
183
- if (response.status !== 401) {
184
- return response;
185
- }
186
-
187
- const authHeader = response.headers.get("WWW-Authenticate");
188
- if (!authHeader) {
189
- return response;
190
- }
191
-
192
- if (
193
- authHeader.includes("invalid_token") ||
194
- authHeader.includes("invalid_request")
195
- ) {
196
- this.log("info", "Request failed due to a missing or expired token.");
197
-
198
- const refresh = await this.refreshTokens();
199
- if (refresh.isFailure) {
200
- this.log("info", "Token refresh failed.");
201
-
202
- // If refresh failed, return the original response.
203
- return response;
204
- }
205
-
206
- // Tokens were refreshed, let's give it another go...
207
- return await fetch(...params);
208
- }
209
-
210
- return response;
211
- }
212
-
213
- async fetch<T, S>(
214
- path: string,
215
- struct: Struct<T, S>,
216
- init?: RequestInit & { json?: object },
217
- ): Promise<Success<Infer<Struct<T, S>>> | Failure<FailurePayload>> {
218
- // If json was provided, we stringify it. Otherwise we use body as is.
219
- const body = init?.json ? JSON.stringify(init.json) : init?.body;
220
-
221
- const headers = new Headers(init?.headers);
222
- if (init?.json) {
223
- headers.set("Content-Type", "application/json");
224
- }
225
-
226
- try {
227
- const url = new URL(path, this.origin);
228
- const res = await this.fetchWithTokenRefresh(url, {
229
- credentials: "include",
230
-
231
- // Overrides
232
- ...init,
233
- body,
234
- headers,
235
- });
236
-
237
- if (!res.ok) {
238
- return new Failure({ type: "API_ERROR", code: res.status });
239
- }
240
-
241
- try {
242
- const data = mask(await res.json(), struct);
243
- return new Success(data);
244
- } catch (error) {
245
- this.log("error", error);
246
-
247
- return new Failure({ type: "VALIDATION_ERROR" });
248
- }
249
- } catch (error) {
250
- this.log("error", error);
251
-
252
- if (error instanceof Error) {
253
- return new Failure({ type: "NETWORK_ERROR" });
254
- }
255
-
256
- return new Failure({ type: "UNKNOWN_ERROR" });
257
- }
258
- }
259
-
260
- /**
261
- * @deprecated Use the instance `fetch` method instead. Token refresh
262
- * is determined dynamically by server response.
263
- */
264
- protected async fetchWithAuth<T, S>(
265
- path: string,
266
- struct: Struct<T, S>,
267
- init?: RequestInit & { json?: object },
268
- ): Promise<Success<Infer<Struct<T, S>>> | Failure<FailurePayload>> {
269
- return this.fetch(path, struct, init);
270
- }
271
-
272
- async login(payload: { email: string; password: string }) {
273
- const result = await this.fetch(
274
- "/v1/sessions",
275
- object({
276
- currentUser: currentUser(),
277
- sessionInfo: sessionInfo(),
278
- }),
279
- {
280
- method: "POST",
281
- json: { email: payload.email, plaintextPassword: payload.password },
282
- },
283
- );
284
-
285
- if (result.isSuccess) {
286
- const { currentUser, sessionInfo } = result.value;
287
- this.dispatchEvent("currentUser", { currentUser });
288
- this.dispatchEvent("sessionInfo", { sessionInfo });
289
- }
290
-
291
- return result;
292
- }
293
-
294
- async userAgent() {
295
- return await this.fetch(
296
- "/ua",
297
- partial({
298
- browser: partial({
299
- name: string(),
300
- version: string(),
301
- major: string(),
302
- }),
303
- device: partial({
304
- type: string(),
305
- model: string(),
306
- vendor: string(),
307
- }),
308
- engine: partial({
309
- name: string(),
310
- version: string(),
311
- }),
312
- os: partial({
313
- name: string(),
314
- version: string(),
315
- }),
316
- }),
317
- );
318
- }
319
-
320
- async logout() {
321
- const result = await this.fetch(
322
- "/v1/sessions",
323
- object({ message: string() }),
324
- { method: "DELETE" },
325
- );
326
-
327
- // Emit sessionExpired to make sure that relevant listeners are run as if the
328
- // session was expired.
329
- this.dispatchEvent("sessionExpired");
330
-
331
- return result;
332
- }
333
-
334
- async join(payload: {
335
- email: string;
336
- password: string;
337
- acceptedTos: boolean;
338
- subscribedToNewsletter: boolean;
339
- }) {
340
- const res = await this.fetch(
341
- "/v1/users",
342
- object({
343
- currentUser: currentUser(),
344
- sessionInfo: sessionInfo(),
345
- tokenId: string(),
346
- }),
347
- {
348
- method: "POST",
349
- json: {
350
- email: payload.email,
351
- plaintextPassword: payload.password,
352
- acceptedTos: payload.acceptedTos,
353
- subscribedToNewsletter: payload.subscribedToNewsletter,
354
- },
355
- },
356
- );
357
-
358
- if (res.isSuccess) {
359
- this.dispatchEvent("currentUser", { currentUser: res.value.currentUser });
360
- this.dispatchEvent("sessionInfo", { sessionInfo: res.value.sessionInfo });
361
- }
362
-
363
- return res;
364
- }
365
-
366
- /**
367
- * Triggers token refresh process.
368
- *
369
- * Note that we do not want to perform multiple concurrent token refresh
370
- * actions, as that will result in unnecessary 401s. For this reason, a
371
- * reference to t
372
- */
373
- async refreshTokens() {
374
- // If there is an ongoing token refresh in progress return that. This should
375
- // only deal the response payload, none of the side-effects and cleanup,
376
- // which will be handled by the initial invocation.
377
- const ongoingRequest = this.refreshTokenPromise;
378
-
379
- if (ongoingRequest) {
380
- this.log("info", "Token refresh ongoing. Reusing existing promise.");
381
- return await ongoingRequest;
382
- }
383
-
384
- // Cache the promise on an instance property to share a reference from
385
- // other potential invocations.
386
- this.refreshTokenPromise = this.fetch(
387
- "/v1/sessions/access-tokens",
388
- object({ sessionInfo: sessionInfo() }),
389
- { method: "POST" },
390
- );
391
-
392
- const result = await this.refreshTokenPromise;
393
-
394
- if (result.isSuccess) {
395
- this.dispatchEvent("sessionInfo", {
396
- sessionInfo: result.value.sessionInfo,
397
- });
398
- }
399
-
400
- if (
401
- result.isFailure &&
402
- result.failure.type === "API_ERROR" &&
403
- result.failure.code === 401
404
- ) {
405
- this.dispatchEvent("sessionExpired");
406
- }
407
-
408
- // Make sure to reset the shared reference so that subsequent invocations
409
- // once again initiate token refresh.
410
- delete this.refreshTokenPromise;
411
-
412
- return result;
413
- }
414
-
415
- async requestPasswordReset(payload: { email: string }) {
416
- return await this.fetch(
417
- `/v1/password-reset-tokens`,
418
- object({ message: string(), tokenId: string() }),
419
- { method: "POST", json: payload },
420
- );
421
- }
422
-
423
- async checkPasswordResetCode(payload: { tokenId: string; code: string }) {
424
- const queryParams = new URLSearchParams({ plaintextCode: payload.code });
425
- return await this.fetch(
426
- `/v1/password-reset-tokens/${payload.tokenId}?${queryParams}`,
427
- object({ message: string() }),
428
- { method: "GET" },
429
- );
430
- }
431
-
432
- async setNewPassword(payload: {
433
- tokenId: string;
434
- code: string;
435
- password: string;
436
- }) {
437
- const queryParams = new URLSearchParams({ plaintextCode: payload.code });
438
- return await this.fetch(
439
- `/v1/password-reset-tokens/${payload.tokenId}?${queryParams}`,
440
- object({ message: string() }),
441
- { method: "PUT", json: { plaintextPassword: payload.password } },
442
- );
443
- }
444
-
445
- async requestUserVerification() {
446
- return await this.fetch(
447
- `/v1/user-verification-tokens`,
448
- object({ message: string(), tokenId: string() }),
449
- { method: "POST" },
450
- );
451
- }
452
-
453
- async verifyUser(payload: { tokenId: string; code: string }) {
454
- const queryParams = new URLSearchParams({ plaintextCode: payload.code });
455
- const req = await this.fetch(
456
- `/v1/user-verification-tokens/${payload.tokenId}?${queryParams}`,
457
- object({ message: string() }),
458
- { method: "PUT" },
459
- );
460
-
461
- if (req.isSuccess) {
462
- await this.refreshTokens();
463
- await this.getCurrentUser();
464
- }
465
-
466
- return req;
467
- }
468
-
469
- async getSnapshot<T, S>(
470
- gameCode: string,
471
- snapshotId: string,
472
- struct: Struct<T, S>,
473
- ) {
474
- return await this.fetch(`/v1/snapshots/${gameCode}/${snapshotId}`, struct);
475
- }
476
-
477
- async createSnapshot(gameCode: string, payload: object) {
478
- return await this.fetch(
479
- `/v1/snapshots/${gameCode}`,
480
- object({ snapshotId: string() }),
481
- { method: "POST", json: payload },
482
- );
483
- }
484
-
485
- async getCurrentUser() {
486
- const result = await this.fetch(`/v1/users/me`, currentUser());
487
-
488
- if (result.isSuccess) {
489
- this.dispatchEvent("currentUser", { currentUser: result.value });
490
- }
491
-
492
- if (
493
- result.isFailure &&
494
- result.failure.type === "API_ERROR" &&
495
- result.failure.code === 404
496
- ) {
497
- // The user no longer exists, so even though they might have a temporarily
498
- // valid session, we want to behave as if the session has expired.
499
- this.dispatchEvent("sessionExpired");
500
- }
501
-
502
- return result;
503
- }
504
-
505
- getRuleset(game: string, version: string) {
506
- return this.fetch(`/v1/rulesets/${game}/${version}`, unknown());
507
- }
508
-
509
- /**
510
- * Uploads a file given S3 presigned config.
511
- */
512
- async uploadFile(
513
- file: File,
514
- presigned: { url: string; key: string; fields: Record<string, string> },
515
- ): Promise<Success<string> | Failure<FailurePayload>> {
516
- const formData = new FormData();
517
-
518
- for (const [key, value] of Object.entries(presigned.fields)) {
519
- formData.append(key, value);
520
- }
521
-
522
- formData.append("file", file);
523
-
524
- try {
525
- const upload = await fetch(presigned.url, {
526
- method: "POST",
527
- body: formData,
528
- });
529
-
530
- if (!upload.ok) {
531
- return new Failure({ type: "API_ERROR", code: upload.status });
532
- }
533
-
534
- return new Success(`/${presigned.key}`);
535
- } catch {
536
- return new Failure({ type: "NETWORK_ERROR" });
537
- }
538
- }
539
-
540
- async redeemPledge(campaignCode: string, id: string) {
541
- return await this.fetch(
542
- `/v1/redemptions/${campaignCode}/pledges/${id}`,
543
- redeemedPledge(),
544
- );
545
- }
546
-
547
- async subscribeToNewsletterByPledgeId(
548
- newsletterCode: string,
549
- pledgeId: string,
550
- ) {
551
- return await this.fetch(
552
- `/v1/newsletters/${newsletterCode}/subscriptions`,
553
- object({ message: string() }),
554
- { method: "POST", json: { pledgeId, type: "PLEDGE" } },
555
- );
556
- }
557
-
558
- async subscribeToNewsletterByEmail(newsletterCode: string, email: string) {
559
- return await this.fetch(
560
- `/v1/newsletters/${newsletterCode}/subscriptions`,
561
- object({ message: string(), tokenId: string() }),
562
- { method: "POST", json: { type: "EMAIL", email } },
563
- );
564
- }
565
-
566
- async confirmNewsletterSignup(
567
- newsletterCode: string,
568
- tokenId: string,
569
- plaintextCode: string,
570
- ) {
571
- const queryParams = new URLSearchParams({ plaintextCode });
572
- return await this.fetch(
573
- `/v1/newsletters/${newsletterCode}/newsletter-signup-tokens/${tokenId}?${queryParams}`,
574
- object({ message: string() }),
575
- { method: "PUT" },
576
- );
577
- }
578
-
579
- async pullUserData(props: {
580
- sinceTs: number | null;
581
- include: GameCode | GameCode[];
582
- expectCurrentUserId: string;
583
- }) {
584
- const params = toParams({
585
- sinceTs: props.sinceTs ?? 0,
586
- include: props.include,
587
- omitDeleted: !props.sinceTs,
588
- expectCurrentUserId: props.expectCurrentUserId,
589
- });
590
-
591
- return await this.fetch(
592
- `/v1/me/game-data?${params}`,
593
- unknown() as Struct<UserGameData, null>,
594
- );
595
- }
596
-
597
- async pushUserData(props: {
598
- currentSyncTs: number;
599
- pullSinceTs: number | null;
600
- data: UserGameData;
601
- expectCurrentUserId: string | null | undefined;
602
- }) {
603
- return await this.fetch(
604
- `/v1/me/game-data`,
605
- unknown() as Struct<UserGameData, null>,
606
- {
607
- method: "PATCH",
608
- json: {
609
- data: props.data,
610
- syncedTs: props.currentSyncTs,
611
- pullSinceTs: props.pullSinceTs ?? 0,
612
- pullOmitDeleted: !props.pullSinceTs,
613
- expectCurrentUserId: props.expectCurrentUserId,
614
- },
615
- },
616
- );
617
- }
618
-
619
- async getUnlocks<T extends FeatureUnlock["gameCode"]>(gameCode: T) {
620
- return this.fetch(
621
- `/v1/me/unlocks/${gameCode}`,
622
- object({
623
- unlocks: array(
624
- featureUnlock() as unknown as Struct<
625
- Extract<FeatureUnlock, { gameCode: T }>,
626
- null
627
- >,
628
- ),
629
- }),
630
- );
631
- }
632
-
633
- async getOwnedProduct(productCode: string) {
634
- return await this.fetch(`/v1/me/products/${productCode}`, ownedProduct());
635
- }
636
-
637
- async startCheckoutSession(payload: {
638
- products: { code: string; quantity: number }[];
639
- successUrl: string;
640
- cancelUrl: string;
641
- }) {
642
- return await this.fetch(
643
- `/v1/stripe/sessions`,
644
- object({ redirectTo: string() }),
645
- {
646
- method: "POST",
647
- json: payload,
648
- },
649
- );
650
- }
651
-
652
- async getCheckoutSession(sessionId: string) {
653
- return await this.fetch(
654
- `/v1/stripe/sessions/${sessionId}`,
655
- object({
656
- status: string(),
657
- orderRef: string(),
658
- products: array(ownedProduct()),
659
- }),
660
- );
661
- }
662
- }
package/lib/common.css.ts DELETED
@@ -1,48 +0,0 @@
1
- import { style } from "@vanilla-extract/css";
2
- import { Hover, MinWidth } from "./media.ts";
3
-
4
- export const itcSymbol = style({
5
- inlineSize: "2.5rem",
6
- blockSize: "2.5rem",
7
- margin: "0rem auto 0.75rem",
8
-
9
- "@media": {
10
- [MinWidth.MEDIUM]: {
11
- marginBlock: "-1rem 1.5rem",
12
- },
13
- },
14
- });
15
-
16
- export const manofa = style({
17
- fontFamily: `"manofa", sans-serif`,
18
- fontFeatureSettings: `"ss01"`,
19
- letterSpacing: "-.01em",
20
- });
21
-
22
- export const minion = style({
23
- fontFamily: `"minion-pro", serif`,
24
- });
25
-
26
- export const itcCard = style([
27
- minion,
28
- {
29
- backgroundColor: "white",
30
- },
31
- ]);
32
-
33
- export const interactiveText = style({
34
- display: "inline",
35
- textDecoration: "underline",
36
- textDecorationColor: "hsl(from currentcolor h s l / 0.3)",
37
- textUnderlineOffset: "0.15em",
38
-
39
- "@media": {
40
- [Hover.HOVER]: {
41
- transition: "text-decoration-color 200ms",
42
-
43
- ":hover": {
44
- textDecorationColor: "hsl(from currentcolor h s l / 1)",
45
- },
46
- },
47
- },
48
- });
@@ -1,10 +0,0 @@
1
- export function copyrightRange(yearSince: number) {
2
- const currentYear = new Date().getFullYear();
3
-
4
- // Handle edge-case in which yearSince is greater than currentYear.
5
- const clampedYearSince = Math.min(yearSince, currentYear);
6
-
7
- return currentYear === clampedYearSince
8
- ? `© ${clampedYearSince}`
9
- : `© ${clampedYearSince}–${currentYear}`;
10
- }