@clicktap/state 0.1.1 → 0.1.3
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/index.js +729 -0
- package/package.json +6 -3
- package/src/auth/AuthProvider.d.ts +49 -0
- package/src/auth/auth.d.ts +52 -0
- package/src/toast/ToastProvider.d.ts +17 -0
- package/src/toast/timer.d.ts +26 -0
- package/src/toast/toast.d.ts +46 -0
- package/.eslintrc.json +0 -40
- package/jest.config.ts +0 -10
- package/project.json +0 -40
- package/src/auth/AuthProvider.tsx +0 -97
- package/src/auth/auth.spec.ts +0 -7
- package/src/auth/auth.ts +0 -408
- package/src/toast/ToastProvider.tsx +0 -44
- package/src/toast/timer.ts +0 -109
- package/src/toast/toast.spec.ts +0 -7
- package/src/toast/toast.ts +0 -222
- package/tsconfig.json +0 -24
- package/tsconfig.lib.json +0 -10
- package/tsconfig.spec.json +0 -14
- /package/src/{index.ts → index.d.ts} +0 -0
package/src/auth/auth.ts
DELETED
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
import { createMachine } from 'xstate';
|
|
2
|
-
import { assign } from '@xstate/immer';
|
|
3
|
-
import type { DoneInvokeEvent } from 'xstate';
|
|
4
|
-
|
|
5
|
-
export interface AuthMachineContext {
|
|
6
|
-
user: {
|
|
7
|
-
email: string;
|
|
8
|
-
username: string;
|
|
9
|
-
firstName: string;
|
|
10
|
-
lastName: string;
|
|
11
|
-
birthdate?: Date;
|
|
12
|
-
gender?: string /** @todo use enum? */;
|
|
13
|
-
createdAt: Date;
|
|
14
|
-
updatedAt: Date;
|
|
15
|
-
role?: string /** @todo use enum? */;
|
|
16
|
-
locked: boolean;
|
|
17
|
-
affiliateId: string;
|
|
18
|
-
stripeId: string;
|
|
19
|
-
} | null;
|
|
20
|
-
accessToken: string;
|
|
21
|
-
refreshToken: string;
|
|
22
|
-
ignoreRefreshToken: boolean;
|
|
23
|
-
endpoints: {
|
|
24
|
-
login: string;
|
|
25
|
-
logout: string;
|
|
26
|
-
refresh: string;
|
|
27
|
-
ssrRefresh: string;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface RefreshTokenEvent {
|
|
32
|
-
type: 'REFRESH_TOKEN';
|
|
33
|
-
accessToken: AuthMachineContext['accessToken'];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface LoginEvent {
|
|
37
|
-
type: 'LOGIN';
|
|
38
|
-
username: string;
|
|
39
|
-
password: string;
|
|
40
|
-
// grant_type: 'password';
|
|
41
|
-
// client_id: string;
|
|
42
|
-
// client_secret: string;
|
|
43
|
-
// scope: 'default';
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface LogoutEvent {
|
|
47
|
-
type: 'LOGOUT';
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface AuthenticateSuccessEvent {
|
|
51
|
-
type: 'AUTHENTICATE_SUCCESS';
|
|
52
|
-
user: AuthMachineContext['user'];
|
|
53
|
-
accessToken: AuthMachineContext['accessToken'];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface RefreshTokenSuccessEvent {
|
|
57
|
-
type: 'REFRESH_TOKEN_SUCCESS';
|
|
58
|
-
accessToken: AuthMachineContext['accessToken'];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// type AuthEvents = RefreshTokenEvent | AnotherEvent;
|
|
62
|
-
export type AuthMachineEvents =
|
|
63
|
-
| RefreshTokenEvent
|
|
64
|
-
| LoginEvent
|
|
65
|
-
| LogoutEvent
|
|
66
|
-
| DoneInvokeEvent<AuthenticateSuccessEvent>
|
|
67
|
-
| DoneInvokeEvent<RefreshTokenSuccessEvent>;
|
|
68
|
-
|
|
69
|
-
type RefreshTokenResponse = {
|
|
70
|
-
accessToken?: string;
|
|
71
|
-
};
|
|
72
|
-
type AuthenticateResponse = {
|
|
73
|
-
accessToken?: string;
|
|
74
|
-
message?: string;
|
|
75
|
-
success: boolean;
|
|
76
|
-
user?: AuthenticateSuccessEvent['user'];
|
|
77
|
-
};
|
|
78
|
-
async function request<T>(url: string, options: object): Promise<T> {
|
|
79
|
-
const response = await fetch(url, options);
|
|
80
|
-
if (response.status === 200) {
|
|
81
|
-
return response.json() as Promise<T>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return Promise.resolve({
|
|
85
|
-
message: response.statusText,
|
|
86
|
-
success: false,
|
|
87
|
-
}) as Promise<T>;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export const authMachine =
|
|
91
|
-
/** @xstate-layout N4IgpgJg5mDOIC5QEMCuAXAFgOjVsAdugJYDGyJBUAxBAPYFjbEEBudA1k3jj4SeUpQELdoOIMA2gAYAujNmJQABzqxiJBkpAAPRAEZ9Adn3YAbABYATAGYr1gKwWLDoxYA0IAJ6JbATmwADkCXPyszK0CzPyijAF84zx5cDEx+MgoWGjAAJxy6HOxlABsKADMCgFsUrBq0ogyhETY6cSk5BW1VdU0CbT0EfWkncysHYelba0DXTx8EBzNTCxDXPzMbaTCjQISk1OxUAj4G8SpaBiZRTm4Do5OBTKpmsUz2+TkutQ0JPqRdRAAWkMAUiNkCxjMO02+gsNjmvkCVmw+hsRnRjiM0hMexAyXuqXSZ2yeQKRVK6AqOWq+OOhNOT2E1zaBAUnX+3R+Wn+A2R0RsmwhRjMUKsRgcSIRCCR2Fs6MCYSmDn0wVxyRyYDKGtgmCyF0YzBaXDq2A1WrguuezLerI6nw5316-UQRhi2BiQ2kk2VkylUSCfnB9iWwSxYrVBzN2stJPyhRK5SqJqjFqyL1aNrZ9pUjt+zoQFn0Zmw0gs6Ks0n0gZMMSlwOx2ELTj80jMTisYvCEdqxToUBgEAA8hhqAAZQcAcQAkgA5dk5np5nm+KElsyBcWWQzhMZSizSQLYVzbhyB8F+Pz6bs4Xv9yBTghjyeDgCqABV5yBOU7l9KjDYghmQJhgVPwHArKw6yrYswKWf8HE2cYJWvbBbwHB9qB0WB0AobgynQXIAAozC9L0AEpqGSND71tRQHUXblQAGKJkVdZwqwlMIokCKCjGwYUYThf8SLRCwEkSEACDoCA4G0HgvgYv4mKBIs+LBIUoUCQT4W8IFIgsRtFmMMskMvKwUIeRosgUrklIBQZKwA1soWccJXU2PwpVEktjBmdcIhiZwUIJfAGSEGyf2UhBAQM0N1gPJYoVbHYoJCHyTCiLYYnXcMJPVTVo2s+jbPzawAkMSZwmxBUJX0KUxUPOV3LYy8tjEvKDmoocMAipcopsAJTzRNY0VhWEpVcaQSy0sVxjGFs0RQrqH16xj7MMOFGxWRYBXBRYeN0hA3XsINnBCfcLyvcSgA */
|
|
92
|
-
createMachine<AuthMachineContext, AuthMachineEvents>(
|
|
93
|
-
{
|
|
94
|
-
context: {
|
|
95
|
-
// id: '',
|
|
96
|
-
user: null,
|
|
97
|
-
accessToken: '',
|
|
98
|
-
refreshToken: '', // will only be on the request from the server side for initial check, does not get passed to the client
|
|
99
|
-
/**
|
|
100
|
-
* If unauthenticating fails, we need a way to ignore the refresh token on subsequent access token refresh.
|
|
101
|
-
* This would be set to true done/error of unauthenticate service, and false on done of authenticate service
|
|
102
|
-
*/
|
|
103
|
-
ignoreRefreshToken: false,
|
|
104
|
-
endpoints: {
|
|
105
|
-
login: '',
|
|
106
|
-
logout: '',
|
|
107
|
-
refresh: '',
|
|
108
|
-
ssrRefresh: '',
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
predictableActionArguments: true,
|
|
112
|
-
id: 'auth',
|
|
113
|
-
initial: 'refreshing',
|
|
114
|
-
states: {
|
|
115
|
-
authenticating: {
|
|
116
|
-
invoke: {
|
|
117
|
-
src: 'authenticate',
|
|
118
|
-
onDone: [
|
|
119
|
-
{
|
|
120
|
-
actions: 'setUserContext',
|
|
121
|
-
target: 'loggedIn',
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
onError: [
|
|
125
|
-
{
|
|
126
|
-
target: 'loggedOut',
|
|
127
|
-
},
|
|
128
|
-
],
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
unauthenticating: {
|
|
132
|
-
invoke: {
|
|
133
|
-
src: 'unauthenticate',
|
|
134
|
-
onDone: [
|
|
135
|
-
{
|
|
136
|
-
actions: 'unsetUserContext',
|
|
137
|
-
target: 'loggedOut',
|
|
138
|
-
},
|
|
139
|
-
],
|
|
140
|
-
onError: [
|
|
141
|
-
{
|
|
142
|
-
actions: ['unsetUserContext', 'setIgnoreRefreshToken'],
|
|
143
|
-
target: 'loggedOut',
|
|
144
|
-
},
|
|
145
|
-
],
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
refreshing: {
|
|
149
|
-
invoke: {
|
|
150
|
-
src: 'refreshAccessToken',
|
|
151
|
-
onDone: [
|
|
152
|
-
{
|
|
153
|
-
actions: ['unsetRefreshToken', 'setAccessToken'],
|
|
154
|
-
target: 'loggedIn',
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
onError: [
|
|
158
|
-
{
|
|
159
|
-
actions: 'unsetRefreshToken',
|
|
160
|
-
target: 'unauthenticating',
|
|
161
|
-
},
|
|
162
|
-
],
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
loggedOut: {
|
|
166
|
-
on: {
|
|
167
|
-
LOGIN: {
|
|
168
|
-
target: 'authenticating',
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
loggedIn: {
|
|
173
|
-
after: {
|
|
174
|
-
'60000': {
|
|
175
|
-
cond: () => typeof window !== 'undefined',
|
|
176
|
-
target: 'refreshing',
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
// entry: ['unsetUserContext'],
|
|
180
|
-
on: {
|
|
181
|
-
LOGOUT: {
|
|
182
|
-
target: 'unauthenticating',
|
|
183
|
-
},
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
actions: {
|
|
190
|
-
// setAccessToken: assign<UserContext, RefreshTokenEvent>((context, event) => {
|
|
191
|
-
// context.accessToken = event.accessToken;
|
|
192
|
-
// }),
|
|
193
|
-
setUserContext: assign(
|
|
194
|
-
(
|
|
195
|
-
context: AuthMachineContext,
|
|
196
|
-
event: DoneInvokeEvent<AuthenticateSuccessEvent>
|
|
197
|
-
) => {
|
|
198
|
-
// event.type - done.invoke.auth.authenticating:invocation[0]
|
|
199
|
-
if (event.data.type !== 'AUTHENTICATE_SUCCESS') return;
|
|
200
|
-
|
|
201
|
-
// context.user = event.data.user;
|
|
202
|
-
context.accessToken = event.data.accessToken;
|
|
203
|
-
context.ignoreRefreshToken = false;
|
|
204
|
-
}
|
|
205
|
-
),
|
|
206
|
-
unsetUserContext: assign((context) => {
|
|
207
|
-
Object.assign(context, authMachine.initialState.context);
|
|
208
|
-
// context = authMachine.initialState.context;
|
|
209
|
-
}),
|
|
210
|
-
setAccessToken: assign(
|
|
211
|
-
(
|
|
212
|
-
context: AuthMachineContext,
|
|
213
|
-
event: DoneInvokeEvent<RefreshTokenSuccessEvent>
|
|
214
|
-
) => {
|
|
215
|
-
if (event.data.type !== 'REFRESH_TOKEN_SUCCESS') return; // is this really necessary?
|
|
216
|
-
|
|
217
|
-
context.accessToken = event.data.accessToken;
|
|
218
|
-
context.ignoreRefreshToken = false;
|
|
219
|
-
}
|
|
220
|
-
),
|
|
221
|
-
unsetRefreshToken: assign(
|
|
222
|
-
(
|
|
223
|
-
context: AuthMachineContext,
|
|
224
|
-
event: DoneInvokeEvent<RefreshTokenSuccessEvent>
|
|
225
|
-
) => {
|
|
226
|
-
if (event.data.type !== 'REFRESH_TOKEN_SUCCESS') return; // is this really necessary?
|
|
227
|
-
|
|
228
|
-
context.refreshToken = '';
|
|
229
|
-
}
|
|
230
|
-
),
|
|
231
|
-
setIgnoreRefreshToken: assign((context) => {
|
|
232
|
-
context.ignoreRefreshToken = true;
|
|
233
|
-
}),
|
|
234
|
-
},
|
|
235
|
-
services: {
|
|
236
|
-
refreshAccessToken: (context) => async () => {
|
|
237
|
-
// eslint-disable-next-line no-console
|
|
238
|
-
// console.log(
|
|
239
|
-
// 'refresh access token (%s)',
|
|
240
|
-
// typeof window === 'undefined' ? 'server' : 'client'
|
|
241
|
-
// );
|
|
242
|
-
// eslint-disable-next-line no-console
|
|
243
|
-
// console.log('--------------------');
|
|
244
|
-
|
|
245
|
-
let response: RefreshTokenResponse;
|
|
246
|
-
if (typeof window === 'undefined') {
|
|
247
|
-
// server
|
|
248
|
-
// eslint-disable-next-line no-console
|
|
249
|
-
// console.log('in server refreshAccessToken');
|
|
250
|
-
// eslint-disable-next-line no-console
|
|
251
|
-
// console.log(context.refreshToken);
|
|
252
|
-
if (context.refreshToken === '') throw new Error('Unauthorized.');
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
response = await request<RefreshTokenResponse>(
|
|
256
|
-
'http://nginx:5210/auth/refresh',
|
|
257
|
-
{
|
|
258
|
-
method: 'POST',
|
|
259
|
-
mode: 'cors',
|
|
260
|
-
credentials: 'include',
|
|
261
|
-
headers: {
|
|
262
|
-
Cookie: `refresh_token=${context.refreshToken}`,
|
|
263
|
-
'Content-Type': 'application/json',
|
|
264
|
-
},
|
|
265
|
-
body: JSON.stringify({
|
|
266
|
-
grant_type: 'refresh_token',
|
|
267
|
-
// client_id: 'default',
|
|
268
|
-
// client_secret: 'Password123!',
|
|
269
|
-
// scope: 'default',
|
|
270
|
-
}).toString(),
|
|
271
|
-
}
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
const data = response;
|
|
275
|
-
|
|
276
|
-
if (typeof data.accessToken === 'undefined') {
|
|
277
|
-
throw new Error('Unauthorized.');
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return {
|
|
281
|
-
type: 'REFRESH_TOKEN_SUCCESS',
|
|
282
|
-
accessToken: data.accessToken,
|
|
283
|
-
} as RefreshTokenSuccessEvent;
|
|
284
|
-
} catch (err) {
|
|
285
|
-
// eslint-disable-next-line no-console
|
|
286
|
-
// console.log(err);
|
|
287
|
-
throw new Error('Could not complete refresh request (server)');
|
|
288
|
-
}
|
|
289
|
-
} else {
|
|
290
|
-
// client
|
|
291
|
-
response = await request<RefreshTokenResponse>(
|
|
292
|
-
'https://middleware-clicktap.local-rmgmedia.com/auth/refresh',
|
|
293
|
-
{
|
|
294
|
-
method: 'POST',
|
|
295
|
-
mode: 'cors',
|
|
296
|
-
credentials: 'include',
|
|
297
|
-
headers: {
|
|
298
|
-
'Content-Type': 'application/json',
|
|
299
|
-
},
|
|
300
|
-
body: JSON.stringify({
|
|
301
|
-
grant_type: 'refresh_token',
|
|
302
|
-
// client_id: 'default',
|
|
303
|
-
// client_secret: 'Password123!',
|
|
304
|
-
// scope: 'default',
|
|
305
|
-
}).toString(),
|
|
306
|
-
}
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
const data = response;
|
|
310
|
-
|
|
311
|
-
if (typeof data.accessToken === 'undefined') {
|
|
312
|
-
throw new Error('Unauthorized.');
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return {
|
|
316
|
-
type: 'REFRESH_TOKEN_SUCCESS',
|
|
317
|
-
accessToken: data.accessToken,
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
authenticate: (context, event) => async () => {
|
|
322
|
-
const e = event as LoginEvent; // workaround to typescript not allowing casting in parameter
|
|
323
|
-
if (e.type !== 'LOGIN') {
|
|
324
|
-
throw new Error(
|
|
325
|
-
`Authenticate can only be called for "LOGIN" event type. Event caller: ${String(
|
|
326
|
-
e.type
|
|
327
|
-
)}`
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const response = await request<AuthenticateResponse>(
|
|
332
|
-
'https://middleware-clicktap.local-rmgmedia.com/auth/login',
|
|
333
|
-
{
|
|
334
|
-
method: 'POST',
|
|
335
|
-
mode: 'cors',
|
|
336
|
-
credentials: 'include',
|
|
337
|
-
headers: {
|
|
338
|
-
'Content-Type': 'application/json',
|
|
339
|
-
},
|
|
340
|
-
body: JSON.stringify({
|
|
341
|
-
username: e.username,
|
|
342
|
-
password: e.password,
|
|
343
|
-
// grant_type: 'password',
|
|
344
|
-
// client_id: 'default',
|
|
345
|
-
// client_secret: 'Password123!',
|
|
346
|
-
// scope: 'default',
|
|
347
|
-
}).toString(),
|
|
348
|
-
}
|
|
349
|
-
);
|
|
350
|
-
/** @todo do we need to handle different status codes here...like what if the service is down? */
|
|
351
|
-
const data = response;
|
|
352
|
-
|
|
353
|
-
if (typeof data.accessToken === 'undefined') {
|
|
354
|
-
throw new Error(
|
|
355
|
-
data.message?.toString() ?? 'Sign in failed. Please try again.'
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return {
|
|
360
|
-
type: 'AUTHENTICATE_SUCCESS',
|
|
361
|
-
// user: data.user,
|
|
362
|
-
accessToken: data.accessToken,
|
|
363
|
-
} as AuthenticateSuccessEvent;
|
|
364
|
-
// /** @todo how to call immer assign from inside invoke here to update context? */
|
|
365
|
-
|
|
366
|
-
// console.log(data);
|
|
367
|
-
// console.log(event);
|
|
368
|
-
},
|
|
369
|
-
unauthenticate: (context) => async () => {
|
|
370
|
-
// const response = await fetch(
|
|
371
|
-
await fetch(
|
|
372
|
-
'https://middleware-clicktap.local-rmgmedia.com/auth/logout',
|
|
373
|
-
{
|
|
374
|
-
method: 'POST',
|
|
375
|
-
mode: 'cors',
|
|
376
|
-
credentials: 'include',
|
|
377
|
-
// headers: {
|
|
378
|
-
// 'Access-Control-Allow-Origin': 'https://middleware-clicktap.local-rmgmedia.com',
|
|
379
|
-
// 'Access-Control-Allow-Credentials': 'true',
|
|
380
|
-
// },
|
|
381
|
-
}
|
|
382
|
-
);
|
|
383
|
-
/** @todo do we need to handle different status codes here...like what if the service is down? */
|
|
384
|
-
// const data = await response.json();
|
|
385
|
-
|
|
386
|
-
return { type: 'UNAUTHENTICATE' };
|
|
387
|
-
|
|
388
|
-
// if (!data.success) {
|
|
389
|
-
// throw new Error(data.message ?? 'Sign in failed. Please try again.');
|
|
390
|
-
// }
|
|
391
|
-
|
|
392
|
-
// return {
|
|
393
|
-
// type: 'AUTHENTICATE_SUCCESS',
|
|
394
|
-
// user: data.user,
|
|
395
|
-
// accessToken: data.accessToken,
|
|
396
|
-
// };
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
// doesn't make sense in the case where you have an access token that is about to expire
|
|
400
|
-
// and the loggedIn state won't fire another refresh for 15 min
|
|
401
|
-
//
|
|
402
|
-
// guards: {
|
|
403
|
-
// checkIfLoggedIn: (context, _event) => {
|
|
404
|
-
// if (context.user) return true;
|
|
405
|
-
// },
|
|
406
|
-
// },
|
|
407
|
-
}
|
|
408
|
-
);
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react';
|
|
2
|
-
import type { InterpreterFrom } from 'xstate';
|
|
3
|
-
import type { ReactNode } from 'react';
|
|
4
|
-
import { toastMachine } from './toast';
|
|
5
|
-
|
|
6
|
-
export const ToastContext = createContext(
|
|
7
|
-
{} as InterpreterFrom<typeof toastMachine>
|
|
8
|
-
);
|
|
9
|
-
|
|
10
|
-
export const useToast = () => {
|
|
11
|
-
return useContext(ToastContext);
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
type Props = {
|
|
15
|
-
children: ReactNode;
|
|
16
|
-
// options: {
|
|
17
|
-
// devTools: boolean;
|
|
18
|
-
// };
|
|
19
|
-
// state: StateConfig<ToastMachineContext, ToastMachineEvents>;
|
|
20
|
-
// setState: Dispatch<
|
|
21
|
-
// SetStateAction<StateConfig<ToastMachineContext, ToastMachineEvents>>
|
|
22
|
-
// >;
|
|
23
|
-
service: InterpreterFrom<typeof toastMachine>;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export function ToastProvider({
|
|
27
|
-
children,
|
|
28
|
-
service,
|
|
29
|
-
}: // options = { devTools: false },
|
|
30
|
-
// state,
|
|
31
|
-
// setState,
|
|
32
|
-
Props) {
|
|
33
|
-
// const toastService = interpret(toastMachine, {
|
|
34
|
-
// devTools: options.devTools,
|
|
35
|
-
// }).start(state);
|
|
36
|
-
|
|
37
|
-
// toastService.subscribe((s) => {
|
|
38
|
-
// setState(s);
|
|
39
|
-
// });
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<ToastContext.Provider value={service}>{children}</ToastContext.Provider>
|
|
43
|
-
);
|
|
44
|
-
}
|
package/src/toast/timer.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { createMachine } from 'xstate';
|
|
2
|
-
import { assign } from '@xstate/immer';
|
|
3
|
-
|
|
4
|
-
export interface TimerContext {
|
|
5
|
-
// The elapsed time (in ms)
|
|
6
|
-
elapsed: number;
|
|
7
|
-
// The maximum time (in ms)
|
|
8
|
-
duration: number;
|
|
9
|
-
// The interval to send TICK events (in ms)
|
|
10
|
-
interval: number;
|
|
11
|
-
// The interval timer
|
|
12
|
-
intervalId: NodeJS.Timer | null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type TimerEvents =
|
|
16
|
-
| {
|
|
17
|
-
// User intent to start the timer
|
|
18
|
-
type: 'START_TIMER';
|
|
19
|
-
}
|
|
20
|
-
| {
|
|
21
|
-
// User intent to resume the timer
|
|
22
|
-
type: 'RESUME_TIMER';
|
|
23
|
-
}
|
|
24
|
-
| {
|
|
25
|
-
// User intent to pause the timer
|
|
26
|
-
type: 'PAUSE_TIMER';
|
|
27
|
-
}
|
|
28
|
-
| {
|
|
29
|
-
// The TICK event sent by the spawned interval service
|
|
30
|
-
type: 'TICK';
|
|
31
|
-
}
|
|
32
|
-
| {
|
|
33
|
-
// User intent to update the duration
|
|
34
|
-
type: 'DURATION.UPDATE';
|
|
35
|
-
value: number;
|
|
36
|
-
}
|
|
37
|
-
| {
|
|
38
|
-
// User intent to reset the elapsed time to 0
|
|
39
|
-
type: 'RESET_TIMER';
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const timerMachine =
|
|
43
|
-
/** @xstate-layout N4IgpgJg5mDOIC5QBcCWBbMAnAdABwEMBXWSAYgCUBRAZQFUBZKgfQBUBJJigbQAYBdRKDwB7WKjQiAdkJAAPRAFoAjADYATDmUBOXQA5tAZmV69AFgDsq1QBoQAT0SHehnAFYAvh7tpMuLERSUqhSUGQcAMIA0nyCSCCi4pIy8QoIJqo4qnoWbnaOCGZmmaq8Zbyqyi5uym6qXj4Y2DgBQSFhAAoAgnQ0LBxcsbKJEqjSsmlVbjimFmZ6agZmbsbq+U4u7g0gvs2twaFkQ-EjyRNObrxZOXkOiGbKFjjq5WbqFurFvNpmXt4gUhEEDgsl2WGGYlG41SSnUVS0um0BmMpks1nW6Tcmk8-zB+GIpAgEKSYxSoDSvAxn142zx+3axKhZPkiEpdwQhjmW1xTVwAGMROg8AAbMDISCMs4whBsgpuOp-DxAA */
|
|
44
|
-
createMachine<TimerContext, TimerEvents>({
|
|
45
|
-
id: 'timer',
|
|
46
|
-
initial: 'running',
|
|
47
|
-
context: {
|
|
48
|
-
elapsed: 0,
|
|
49
|
-
duration: 3000,
|
|
50
|
-
interval: 100,
|
|
51
|
-
intervalId: null,
|
|
52
|
-
},
|
|
53
|
-
states: {
|
|
54
|
-
paused: {
|
|
55
|
-
on: {
|
|
56
|
-
// START_TIMER: {
|
|
57
|
-
// target: 'running',
|
|
58
|
-
// cond: (context) => context.elapsed < context.duration,
|
|
59
|
-
// },
|
|
60
|
-
RESUME_TIMER: {
|
|
61
|
-
target: 'running',
|
|
62
|
-
cond: (context) => context.elapsed < context.duration,
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
running: {
|
|
67
|
-
invoke: {
|
|
68
|
-
src: (context) => (send) => {
|
|
69
|
-
// eslint-disable-next-line no-console
|
|
70
|
-
// console.log('context.interval: ', context.interval);
|
|
71
|
-
const interval = setInterval(() => {
|
|
72
|
-
send('TICK');
|
|
73
|
-
}, context.interval);
|
|
74
|
-
return () => {
|
|
75
|
-
clearInterval(interval);
|
|
76
|
-
};
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
always: [
|
|
80
|
-
{
|
|
81
|
-
target: 'completed',
|
|
82
|
-
cond: (context) => {
|
|
83
|
-
return context.elapsed >= context.duration;
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
on: {
|
|
88
|
-
TICK: {
|
|
89
|
-
actions: assign((context, event) => {
|
|
90
|
-
if (event.type !== 'TICK') return;
|
|
91
|
-
context.elapsed += context.interval;
|
|
92
|
-
// eslint-disable-next-line no-console
|
|
93
|
-
// console.log(
|
|
94
|
-
// 'elapsed: %d | interval: %d',
|
|
95
|
-
// context.elapsed,
|
|
96
|
-
// context.interval
|
|
97
|
-
// );
|
|
98
|
-
}),
|
|
99
|
-
},
|
|
100
|
-
PAUSE_TIMER: { target: 'paused' },
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
completed: {
|
|
104
|
-
type: 'final',
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
export default timerMachine;
|