@owlmeans/web-flow 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +411 -0
- package/build/.gitkeep +0 -0
- package/build/consts.d.ts +3 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +3 -0
- package/build/consts.js.map +1 -0
- package/build/helper.d.ts +3 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +24 -0
- package/build/helper.js.map +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/service.d.ts +5 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +78 -0
- package/build/service.js.map +1 -0
- package/build/types.d.ts +5 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +45 -0
- package/src/consts.ts +4 -0
- package/src/helper.ts +27 -0
- package/src/index.ts +4 -0
- package/src/service.ts +99 -0
- package/src/types.ts +6 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
# @owlmeans/web-flow
|
|
2
|
+
|
|
3
|
+
Web-specific flow management library for OwlMeans Common applications. This package extends `@owlmeans/client-flow` with web browser-specific functionality including URL-based flow state management, browser navigation integration, and query parameter handling.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@owlmeans/web-flow` package provides web-specific implementations of the OwlMeans flow system. It offers:
|
|
8
|
+
|
|
9
|
+
- **URL-Based Flow State**: Flow state management through browser URLs and query parameters
|
|
10
|
+
- **Browser Navigation**: Integration with browser history and navigation APIs
|
|
11
|
+
- **Resource Persistence**: Web-specific flow state persistence using browser storage
|
|
12
|
+
- **Query Parameter Management**: Automatic flow state serialization in URL parameters
|
|
13
|
+
- **Module Integration**: Enhanced module integration for web-based flow transitions
|
|
14
|
+
- **Client-Side Routing**: Seamless integration with React Router for flow navigation
|
|
15
|
+
|
|
16
|
+
This package is part of the OwlMeans flow management quadra:
|
|
17
|
+
- **@owlmeans/flow**: Common flow interfaces and utilities
|
|
18
|
+
- **@owlmeans/client-flow**: Client-side flow implementation
|
|
19
|
+
- **@owlmeans/web-flow**: Web-specific flow implementation *(this package)*
|
|
20
|
+
- **@owlmeans/server-flow**: Server-side flow processing
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @owlmeans/web-flow react react-router-dom
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Core Concepts
|
|
29
|
+
|
|
30
|
+
### Web Flow Service
|
|
31
|
+
Extends the basic flow service with web-specific capabilities like URL management and browser navigation.
|
|
32
|
+
|
|
33
|
+
### URL State Management
|
|
34
|
+
Flow state is automatically serialized and stored in URL query parameters, allowing flows to survive page reloads and be shareable.
|
|
35
|
+
|
|
36
|
+
### Browser Integration
|
|
37
|
+
Full integration with browser APIs for navigation, history management, and URL manipulation.
|
|
38
|
+
|
|
39
|
+
## API Reference
|
|
40
|
+
|
|
41
|
+
### Factory Functions
|
|
42
|
+
|
|
43
|
+
#### `makeFlowService(alias?: string): FlowService`
|
|
44
|
+
|
|
45
|
+
Creates a web-specific flow service with enhanced browser integration.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { makeFlowService } from '@owlmeans/web-flow'
|
|
49
|
+
|
|
50
|
+
const webFlowService = makeFlowService('web-flow')
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Parameters:**
|
|
54
|
+
- `alias`: string (optional) - Service alias for registration, defaults to 'flow'
|
|
55
|
+
|
|
56
|
+
**Returns:** Enhanced FlowService with web-specific capabilities
|
|
57
|
+
|
|
58
|
+
#### `appendWebFlowService<C, T>(context: T, alias?: string): T`
|
|
59
|
+
|
|
60
|
+
Appends a web flow service to the application context with necessary resource setup.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { appendWebFlowService } from '@owlmeans/web-flow'
|
|
64
|
+
import { makeClientContext } from '@owlmeans/client-context'
|
|
65
|
+
|
|
66
|
+
const context = makeClientContext(config)
|
|
67
|
+
appendWebFlowService(context)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Parameters:**
|
|
71
|
+
- `context`: T - The client context to append the service to
|
|
72
|
+
- `alias`: string (optional) - Service alias, defaults to 'flow'
|
|
73
|
+
|
|
74
|
+
**Returns:** Enhanced context with web flow service and resources
|
|
75
|
+
|
|
76
|
+
### Enhanced Methods
|
|
77
|
+
|
|
78
|
+
#### `proceed(req?: Partial<AbstractRequest>, dryRun?: boolean): Promise<string>`
|
|
79
|
+
|
|
80
|
+
Web-specific implementation of flow progression with URL management.
|
|
81
|
+
|
|
82
|
+
**Behavior**:
|
|
83
|
+
- Calls the appropriate module for the current flow step
|
|
84
|
+
- Serializes flow state into URL query parameters
|
|
85
|
+
- Handles browser navigation and URL updates
|
|
86
|
+
- Integrates with React Router for seamless transitions
|
|
87
|
+
|
|
88
|
+
**Usage**: Advancing through flow steps with automatic URL updates
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
const webFlowService = context.service<FlowService>('flow')
|
|
92
|
+
|
|
93
|
+
// Proceed to next step with automatic URL update
|
|
94
|
+
await webFlowService.proceed({
|
|
95
|
+
body: { userInput: 'data' }
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Configuration
|
|
100
|
+
|
|
101
|
+
#### Flow Configuration Options
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
interface WebFlowConfig extends FlowConfig {
|
|
105
|
+
queryParam?: string // URL query parameter name for flow state
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Constants
|
|
110
|
+
|
|
111
|
+
#### `QUERY_PARAM`
|
|
112
|
+
Default query parameter name for flow state (`'flow'`).
|
|
113
|
+
|
|
114
|
+
## Usage Examples
|
|
115
|
+
|
|
116
|
+
### Basic Web Flow Setup
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { appendWebFlowService } from '@owlmeans/web-flow'
|
|
120
|
+
import { makeClientContext } from '@owlmeans/client-context'
|
|
121
|
+
|
|
122
|
+
// Create context with web flow
|
|
123
|
+
const context = makeClientContext({
|
|
124
|
+
service: 'my-web-app',
|
|
125
|
+
flowConfig: {
|
|
126
|
+
defaultFlow: 'user-onboarding',
|
|
127
|
+
queryParam: 'state' // Custom query parameter name
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
appendWebFlowService(context)
|
|
132
|
+
|
|
133
|
+
// Initialize
|
|
134
|
+
await context.configure().init()
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### URL-Aware Flow Component
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { useContext } from '@owlmeans/client'
|
|
141
|
+
import { useSearchParams } from 'react-router-dom'
|
|
142
|
+
import { useEffect, useState } from 'react'
|
|
143
|
+
|
|
144
|
+
function WebFlowComponent() {
|
|
145
|
+
const context = useContext()
|
|
146
|
+
const [searchParams] = useSearchParams()
|
|
147
|
+
const [currentStep, setCurrentStep] = useState(null)
|
|
148
|
+
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
const initFlow = async () => {
|
|
151
|
+
const flowService = context.service('flow')
|
|
152
|
+
|
|
153
|
+
// Check if flow state exists in URL
|
|
154
|
+
const flowState = searchParams.get('flow')
|
|
155
|
+
let flow
|
|
156
|
+
|
|
157
|
+
if (flowState) {
|
|
158
|
+
// Restore flow from URL parameter
|
|
159
|
+
flow = await flowService.load(flowState)
|
|
160
|
+
} else {
|
|
161
|
+
// Start new flow
|
|
162
|
+
flow = await flowService.begin('user-onboarding')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
setCurrentStep(flow.step())
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
initFlow()
|
|
169
|
+
}, [searchParams])
|
|
170
|
+
|
|
171
|
+
const handleNext = async () => {
|
|
172
|
+
const flowService = context.service('flow')
|
|
173
|
+
await flowService.proceed({ action: 'next' })
|
|
174
|
+
|
|
175
|
+
// Flow state is automatically updated in URL
|
|
176
|
+
const updatedFlow = await flowService.state()
|
|
177
|
+
setCurrentStep(updatedFlow?.step())
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div>
|
|
182
|
+
<h2>Current Step: {currentStep}</h2>
|
|
183
|
+
<button onClick={handleNext}>Next Step</button>
|
|
184
|
+
</div>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Shareable Flow URLs
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
function ShareableFlow() {
|
|
193
|
+
const context = useContext()
|
|
194
|
+
|
|
195
|
+
const getShareableURL = async () => {
|
|
196
|
+
const flowService = context.service('flow')
|
|
197
|
+
const flow = await flowService.state()
|
|
198
|
+
|
|
199
|
+
if (flow) {
|
|
200
|
+
const currentURL = new URL(window.location.href)
|
|
201
|
+
currentURL.searchParams.set('flow', flow.serialize())
|
|
202
|
+
return currentURL.toString()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return window.location.href
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const handleShare = async () => {
|
|
209
|
+
const shareableURL = await getShareableURL()
|
|
210
|
+
|
|
211
|
+
if (navigator.share) {
|
|
212
|
+
await navigator.share({
|
|
213
|
+
title: 'Continue Flow',
|
|
214
|
+
url: shareableURL
|
|
215
|
+
})
|
|
216
|
+
} else {
|
|
217
|
+
// Fallback to clipboard
|
|
218
|
+
await navigator.clipboard.writeText(shareableURL)
|
|
219
|
+
alert('URL copied to clipboard!')
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<button onClick={handleShare}>
|
|
225
|
+
Share Flow Progress
|
|
226
|
+
</button>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Browser Navigation Integration
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { useNavigate, useLocation } from 'react-router-dom'
|
|
235
|
+
import { useContext } from '@owlmeans/client'
|
|
236
|
+
|
|
237
|
+
function NavigationAwareFlow() {
|
|
238
|
+
const context = useContext()
|
|
239
|
+
const navigate = useNavigate()
|
|
240
|
+
const location = useLocation()
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
// Listen for browser back/forward navigation
|
|
244
|
+
const handlePopState = async () => {
|
|
245
|
+
const flowService = context.service('flow')
|
|
246
|
+
const searchParams = new URLSearchParams(location.search)
|
|
247
|
+
const flowState = searchParams.get('flow')
|
|
248
|
+
|
|
249
|
+
if (flowState) {
|
|
250
|
+
await flowService.load(flowState)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
window.addEventListener('popstate', handlePopState)
|
|
255
|
+
return () => window.removeEventListener('popstate', handlePopState)
|
|
256
|
+
}, [location])
|
|
257
|
+
|
|
258
|
+
const proceedToStep = async (stepName: string) => {
|
|
259
|
+
const flowService = context.service('flow')
|
|
260
|
+
await flowService.proceed({ target: stepName })
|
|
261
|
+
|
|
262
|
+
// Navigation is handled automatically by the web flow service
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<div>
|
|
267
|
+
<button onClick={() => proceedToStep('step1')}>Go to Step 1</button>
|
|
268
|
+
<button onClick={() => proceedToStep('step2')}>Go to Step 2</button>
|
|
269
|
+
<button onClick={() => navigate(-1)}>Browser Back</button>
|
|
270
|
+
</div>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Custom Query Parameter Configuration
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
const context = makeClientContext({
|
|
279
|
+
service: 'custom-app',
|
|
280
|
+
flowConfig: {
|
|
281
|
+
defaultFlow: 'checkout',
|
|
282
|
+
queryParam: 'checkout-state', // Custom parameter name
|
|
283
|
+
services: {
|
|
284
|
+
payment: 'payment-service',
|
|
285
|
+
shipping: 'shipping-service'
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
appendWebFlowService(context)
|
|
291
|
+
|
|
292
|
+
// URLs will look like: /checkout?checkout-state=serialized-flow-data
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Flow State Persistence
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
class WebFlowManager {
|
|
299
|
+
constructor(private context: ClientContext) {}
|
|
300
|
+
|
|
301
|
+
async saveFlowToURL(): Promise<string> {
|
|
302
|
+
const flowService = this.context.service('flow')
|
|
303
|
+
const flow = await flowService.state()
|
|
304
|
+
|
|
305
|
+
if (flow) {
|
|
306
|
+
const url = new URL(window.location.href)
|
|
307
|
+
url.searchParams.set('flow', flow.serialize())
|
|
308
|
+
|
|
309
|
+
// Update browser URL without navigation
|
|
310
|
+
window.history.replaceState({}, '', url.toString())
|
|
311
|
+
|
|
312
|
+
return url.toString()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return window.location.href
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async loadFlowFromURL(): Promise<boolean> {
|
|
319
|
+
const searchParams = new URLSearchParams(window.location.search)
|
|
320
|
+
const flowState = searchParams.get('flow')
|
|
321
|
+
|
|
322
|
+
if (flowState) {
|
|
323
|
+
const flowService = this.context.service('flow')
|
|
324
|
+
await flowService.load(flowState)
|
|
325
|
+
return true
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return false
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
clearFlowFromURL(): void {
|
|
332
|
+
const url = new URL(window.location.href)
|
|
333
|
+
url.searchParams.delete('flow')
|
|
334
|
+
window.history.replaceState({}, '', url.toString())
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Usage
|
|
339
|
+
const flowManager = new WebFlowManager(context)
|
|
340
|
+
|
|
341
|
+
// Save current flow state to URL
|
|
342
|
+
await flowManager.saveFlowToURL()
|
|
343
|
+
|
|
344
|
+
// Restore flow from URL on page load
|
|
345
|
+
const restored = await flowManager.loadFlowFromURL()
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Integration with React Router
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
|
352
|
+
import { appendWebFlowService } from '@owlmeans/web-flow'
|
|
353
|
+
|
|
354
|
+
function App() {
|
|
355
|
+
const context = makeClientContext(config)
|
|
356
|
+
appendWebFlowService(context)
|
|
357
|
+
|
|
358
|
+
return (
|
|
359
|
+
<BrowserRouter>
|
|
360
|
+
<Routes>
|
|
361
|
+
<Route path="/flow/*" element={<FlowRoutes />} />
|
|
362
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
363
|
+
</Routes>
|
|
364
|
+
</BrowserRouter>
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function FlowRoutes() {
|
|
369
|
+
const context = useContext()
|
|
370
|
+
const [searchParams] = useSearchParams()
|
|
371
|
+
|
|
372
|
+
// Flow state is automatically managed through URL
|
|
373
|
+
const flowState = searchParams.get('flow')
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<div>
|
|
377
|
+
{flowState ? (
|
|
378
|
+
<ActiveFlowComponent />
|
|
379
|
+
) : (
|
|
380
|
+
<StartFlowComponent />
|
|
381
|
+
)}
|
|
382
|
+
</div>
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Best Practices
|
|
388
|
+
|
|
389
|
+
1. **URL Management**: Use meaningful query parameter names for flow state
|
|
390
|
+
2. **Browser Integration**: Respect browser navigation patterns
|
|
391
|
+
3. **State Serialization**: Keep flow state minimal for URL storage
|
|
392
|
+
4. **Error Handling**: Handle URL parsing errors gracefully
|
|
393
|
+
5. **SEO Considerations**: Use appropriate meta tags for flow pages
|
|
394
|
+
6. **Performance**: Optimize flow state serialization for large flows
|
|
395
|
+
7. **User Experience**: Provide clear navigation indicators
|
|
396
|
+
|
|
397
|
+
## Dependencies
|
|
398
|
+
|
|
399
|
+
This package depends on:
|
|
400
|
+
- `@owlmeans/client-flow` - Base client flow implementation
|
|
401
|
+
- `@owlmeans/client` - React client library
|
|
402
|
+
- `@owlmeans/flow` - Core flow system
|
|
403
|
+
- `react` - React library (peer dependency)
|
|
404
|
+
- `react-router-dom` - React Router DOM (peer dependency)
|
|
405
|
+
|
|
406
|
+
## Related Packages
|
|
407
|
+
|
|
408
|
+
- [`@owlmeans/client-flow`](../client-flow) - Base client flow implementation
|
|
409
|
+
- [`@owlmeans/flow`](../flow) - Core flow system
|
|
410
|
+
- [`@owlmeans/server-flow`](../server-flow) - Server-side flow processing
|
|
411
|
+
- [`@owlmeans/client`](../client) - React client library
|
package/build/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,WAAW,SAAS,CAAA;AAEjC,eAAO,MAAM,aAAa,YAAY,CAAA"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAA;AAEjC,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,uBAAuB,CAAA;AAKpE,eAAO,MAAM,OAAO,YAAY,MAAM,GAAG,IAAI,KAAU,UAAU,GAAG,IAmBnE,CAAA"}
|
package/build/helper.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useContext, useModule, useNavigate, useValue } from '@owlmeans/client';
|
|
2
|
+
import { createFlowClient } from '@owlmeans/client-flow';
|
|
3
|
+
import { DEFAULT_ALIAS as FLOW_ALIAS } from '@owlmeans/client-flow';
|
|
4
|
+
import { useSearchParams } from 'react-router-dom';
|
|
5
|
+
import { QUERY_PARAM, SERVICE_PARAM } from './consts.js';
|
|
6
|
+
export const useFlow = (target = null) => {
|
|
7
|
+
const context = useContext();
|
|
8
|
+
const nav = useNavigate();
|
|
9
|
+
const [query] = useSearchParams();
|
|
10
|
+
const { params } = useModule();
|
|
11
|
+
const client = useValue(async () => {
|
|
12
|
+
if (QUERY_PARAM in params && params[QUERY_PARAM] != null) {
|
|
13
|
+
const service = context.service(FLOW_ALIAS);
|
|
14
|
+
await service.ready();
|
|
15
|
+
return createFlowClient(context, nav).setup(await service.load(params[QUERY_PARAM]));
|
|
16
|
+
}
|
|
17
|
+
return createFlowClient(context, nav).boot(query.get(SERVICE_PARAM) ?? target);
|
|
18
|
+
}, [
|
|
19
|
+
QUERY_PARAM in params && params[QUERY_PARAM],
|
|
20
|
+
query.get(QUERY_PARAM), query.get(SERVICE_PARAM) ?? target
|
|
21
|
+
]);
|
|
22
|
+
return client;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,EAAE,aAAa,IAAI,UAAU,EAAE,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAExD,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,SAAwB,IAAI,EAAqB,EAAE;IACzE,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,GAAG,GAAG,WAAW,EAAE,CAAA;IACzB,MAAM,CAAC,KAAK,CAAC,GAAG,eAAe,EAAE,CAAA;IACjC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAAA;IAC9B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,IAAI,EAAE;QACjC,IAAI,WAAW,IAAI,MAAM,IAAI,MAAM,CAAC,WAAW,CAAC,IAAI,IAAI,EAAE,CAAC;YACzD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAc,UAAU,CAAC,CAAA;YACxD,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA;YAErB,OAAO,gBAAgB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAW,CAAC,CAAC,CAAA;QAChG,CAAC;QACD,OAAO,gBAAgB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,MAAM,CAAC,CAAA;IAChF,CAAC,EAAE;QACD,WAAW,IAAI,MAAM,IAAI,MAAM,CAAC,WAAW,CAAW;QACtD,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,MAAM;KAC3D,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC,CAAA"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ClientContext, ClientConfig } from '@owlmeans/client-context';
|
|
2
|
+
import type { FlowService } from './types.js';
|
|
3
|
+
export declare const makeFlowService: (alias?: string) => FlowService;
|
|
4
|
+
export declare const appendFlowService: <C extends ClientConfig, T extends ClientContext<C>>(ctx: T, alias?: string) => T;
|
|
5
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAQ7C,eAAO,MAAM,eAAe,WAAW,MAAM,KAAmB,WA4E/D,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,aAAa,CAAC,CAAC,CAAC,OAC7C,CAAC,UAAS,MAAM,KAAmB,CAQzC,CAAA"}
|
package/build/service.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { DEFAULT_ALIAS, makeBasicFlowService, FLOW_STATE } from '@owlmeans/client-flow';
|
|
2
|
+
import { QUERY_PARAM } from './consts.js';
|
|
3
|
+
import { FlowStepMissconfigured, makeFlowModel, UnknownTransition } from '@owlmeans/flow';
|
|
4
|
+
import { ResilientError } from '@owlmeans/error';
|
|
5
|
+
import { assertContext } from '@owlmeans/context';
|
|
6
|
+
import { appendClientResource } from '@owlmeans/client-resource';
|
|
7
|
+
export const makeFlowService = (alias = DEFAULT_ALIAS) => {
|
|
8
|
+
const location = `web-flow-service:${alias}`;
|
|
9
|
+
const service = makeBasicFlowService(alias);
|
|
10
|
+
// @TODO Use in the client proceed also (unify the code)
|
|
11
|
+
service.proceed = async (req, dryRun = false) => {
|
|
12
|
+
const ctx = assertContext(service.ctx, location);
|
|
13
|
+
const flow = service.flow;
|
|
14
|
+
if (flow == null) {
|
|
15
|
+
throw new UnknownTransition('service.proceed');
|
|
16
|
+
}
|
|
17
|
+
const step = flow.step();
|
|
18
|
+
if (step.module == null) {
|
|
19
|
+
throw new FlowStepMissconfigured(step.step);
|
|
20
|
+
}
|
|
21
|
+
const cfg = service.config();
|
|
22
|
+
const param = cfg.queryParam ?? QUERY_PARAM;
|
|
23
|
+
const module = ctx.module(step.module);
|
|
24
|
+
const [url] = await module.call({
|
|
25
|
+
...req,
|
|
26
|
+
params: { ...req?.params, [param]: flow.serialize() },
|
|
27
|
+
full: true
|
|
28
|
+
});
|
|
29
|
+
// const params = new URLSearchParams(req?.query ?? {})
|
|
30
|
+
// params.set(param, flow.serialize())
|
|
31
|
+
const redirectUrl = new URL(url);
|
|
32
|
+
redirectUrl.searchParams.set(param, flow.serialize());
|
|
33
|
+
if (!dryRun) {
|
|
34
|
+
document.location.href = redirectUrl.toString();
|
|
35
|
+
}
|
|
36
|
+
return redirectUrl.toString();
|
|
37
|
+
};
|
|
38
|
+
service.goHome = async (alias, dryRun = false) => {
|
|
39
|
+
const ctx = assertContext(service.ctx, location);
|
|
40
|
+
const cfg = await ctx.config;
|
|
41
|
+
const targetAlias = alias ?? Object.values(cfg.services).find(s => s.default)?.service ?? cfg.service;
|
|
42
|
+
const target = ctx.serviceRoute(targetAlias);
|
|
43
|
+
const url = target.home ?? cfg.brand?.home ?? 'https://owlmeans.com';
|
|
44
|
+
if (!dryRun) {
|
|
45
|
+
document.location.href = url;
|
|
46
|
+
}
|
|
47
|
+
return url;
|
|
48
|
+
};
|
|
49
|
+
const init = service.lazyInit;
|
|
50
|
+
service.lazyInit = async () => {
|
|
51
|
+
await init();
|
|
52
|
+
const cfg = service.config();
|
|
53
|
+
const param = cfg.queryParam ?? QUERY_PARAM;
|
|
54
|
+
const url = new URL(window.location.href);
|
|
55
|
+
const state = url.searchParams.get(param);
|
|
56
|
+
if (state == null) {
|
|
57
|
+
service.flow = null;
|
|
58
|
+
service.resolvePair().resolve(false);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
service.flow = await makeFlowModel(state, service.provideFlow);
|
|
63
|
+
service.resolvePair().resolve(true);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
service.flow = null;
|
|
67
|
+
service.resolvePair().reject(ResilientError.ensure(e));
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
return service;
|
|
71
|
+
};
|
|
72
|
+
export const appendFlowService = (ctx, alias = DEFAULT_ALIAS) => {
|
|
73
|
+
const service = makeFlowService(alias);
|
|
74
|
+
ctx.registerService(service);
|
|
75
|
+
appendClientResource(ctx, FLOW_STATE);
|
|
76
|
+
return ctx;
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAExE,MAAM,WAAW,WAAY,SAAQ,iBAAiB;IACpD,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC9D"}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owlmeans/web-flow",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc -b",
|
|
7
|
+
"dev": "sleep 366 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
|
|
8
|
+
"watch": "tsc -b -w --preserveWatchOutput --pretty"
|
|
9
|
+
},
|
|
10
|
+
"main": "build/index.js",
|
|
11
|
+
"module": "build/index.js",
|
|
12
|
+
"types": "build/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./build/index.js",
|
|
16
|
+
"require": "./build/index.js",
|
|
17
|
+
"default": "./build/index.js",
|
|
18
|
+
"module": "./build/index.js",
|
|
19
|
+
"types": "./build/index.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@owlmeans/client": "^0.1.0",
|
|
24
|
+
"@owlmeans/client-context": "^0.1.0",
|
|
25
|
+
"@owlmeans/client-flow": "^0.1.0",
|
|
26
|
+
"@owlmeans/client-module": "^0.1.0",
|
|
27
|
+
"@owlmeans/client-resource": "^0.1.0",
|
|
28
|
+
"@owlmeans/context": "^0.1.0",
|
|
29
|
+
"@owlmeans/error": "^0.1.0",
|
|
30
|
+
"@owlmeans/flow": "^0.1.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"react": "*",
|
|
34
|
+
"react-router-dom": "*"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"nodemon": "^3.1.7",
|
|
38
|
+
"npm-check": "^6.0.1",
|
|
39
|
+
"typescript": "^5.6.3"
|
|
40
|
+
},
|
|
41
|
+
"private": false,
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/consts.ts
ADDED
package/src/helper.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useContext, useModule, useNavigate, useValue } from '@owlmeans/client'
|
|
2
|
+
import { createFlowClient } from '@owlmeans/client-flow'
|
|
3
|
+
import type { FlowClient, FlowService } from '@owlmeans/client-flow'
|
|
4
|
+
import { DEFAULT_ALIAS as FLOW_ALIAS } from '@owlmeans/client-flow'
|
|
5
|
+
import { useSearchParams } from 'react-router-dom'
|
|
6
|
+
import { QUERY_PARAM, SERVICE_PARAM } from './consts.js'
|
|
7
|
+
|
|
8
|
+
export const useFlow = (target: string | null = null): FlowClient | null => {
|
|
9
|
+
const context = useContext()
|
|
10
|
+
const nav = useNavigate()
|
|
11
|
+
const [query] = useSearchParams()
|
|
12
|
+
const { params } = useModule()
|
|
13
|
+
const client = useValue(async () => {
|
|
14
|
+
if (QUERY_PARAM in params && params[QUERY_PARAM] != null) {
|
|
15
|
+
const service = context.service<FlowService>(FLOW_ALIAS)
|
|
16
|
+
await service.ready()
|
|
17
|
+
|
|
18
|
+
return createFlowClient(context, nav).setup(await service.load(params[QUERY_PARAM] as string))
|
|
19
|
+
}
|
|
20
|
+
return createFlowClient(context, nav).boot(query.get(SERVICE_PARAM) ?? target)
|
|
21
|
+
}, [
|
|
22
|
+
QUERY_PARAM in params && params[QUERY_PARAM] as string,
|
|
23
|
+
query.get(QUERY_PARAM), query.get(SERVICE_PARAM) ?? target
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
return client
|
|
27
|
+
}
|
package/src/index.ts
ADDED
package/src/service.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { ClientContext, ClientConfig } from '@owlmeans/client-context'
|
|
2
|
+
import { DEFAULT_ALIAS, makeBasicFlowService, FLOW_STATE } from '@owlmeans/client-flow'
|
|
3
|
+
import type { FlowService } from './types.js'
|
|
4
|
+
import { QUERY_PARAM } from './consts.js'
|
|
5
|
+
import { FlowStepMissconfigured, makeFlowModel, UnknownTransition } from '@owlmeans/flow'
|
|
6
|
+
import { ResilientError } from '@owlmeans/error'
|
|
7
|
+
import { assertContext } from '@owlmeans/context'
|
|
8
|
+
import type { ClientModule } from '@owlmeans/client-module'
|
|
9
|
+
import { appendClientResource } from '@owlmeans/client-resource'
|
|
10
|
+
|
|
11
|
+
export const makeFlowService = (alias: string = DEFAULT_ALIAS): FlowService => {
|
|
12
|
+
const location = `web-flow-service:${alias}`
|
|
13
|
+
const service: FlowService = makeBasicFlowService(alias) as FlowService
|
|
14
|
+
|
|
15
|
+
// @TODO Use in the client proceed also (unify the code)
|
|
16
|
+
service.proceed = async (req, dryRun = false) => {
|
|
17
|
+
const ctx = assertContext(service.ctx, location) as ClientContext
|
|
18
|
+
const flow = service.flow
|
|
19
|
+
if (flow == null) {
|
|
20
|
+
throw new UnknownTransition('service.proceed')
|
|
21
|
+
}
|
|
22
|
+
const step = flow.step()
|
|
23
|
+
if (step.module == null) {
|
|
24
|
+
throw new FlowStepMissconfigured(step.step)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const cfg = service.config()
|
|
28
|
+
const param = cfg.queryParam ?? QUERY_PARAM
|
|
29
|
+
|
|
30
|
+
const module = ctx.module<ClientModule<string>>(step.module)
|
|
31
|
+
const [url] = await module.call<string>({
|
|
32
|
+
...req,
|
|
33
|
+
params: { ...req?.params, [param]: flow.serialize() },
|
|
34
|
+
full: true
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// const params = new URLSearchParams(req?.query ?? {})
|
|
38
|
+
// params.set(param, flow.serialize())
|
|
39
|
+
|
|
40
|
+
const redirectUrl = new URL(url)
|
|
41
|
+
redirectUrl.searchParams.set(param, flow.serialize())
|
|
42
|
+
|
|
43
|
+
if (!dryRun) {
|
|
44
|
+
document.location.href = redirectUrl.toString()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return redirectUrl.toString()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
service.goHome = async (alias, dryRun = false) => {
|
|
51
|
+
const ctx = assertContext(service.ctx, location) as ClientContext
|
|
52
|
+
const cfg = await ctx.config
|
|
53
|
+
const targetAlias = alias ?? Object.values(cfg.services).find(s => s.default)?.service ?? cfg.service
|
|
54
|
+
const target = ctx.serviceRoute(targetAlias)
|
|
55
|
+
|
|
56
|
+
const url = target.home ?? cfg.brand?.home ?? 'https://owlmeans.com'
|
|
57
|
+
if (!dryRun) {
|
|
58
|
+
document.location.href = url
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return url
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const init = service.lazyInit
|
|
65
|
+
service.lazyInit = async () => {
|
|
66
|
+
await init()
|
|
67
|
+
const cfg = service.config()
|
|
68
|
+
const param = cfg.queryParam ?? QUERY_PARAM
|
|
69
|
+
const url = new URL(window.location.href)
|
|
70
|
+
const state = url.searchParams.get(param)
|
|
71
|
+
if (state == null) {
|
|
72
|
+
service.flow = null
|
|
73
|
+
service.resolvePair().resolve(false)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
service.flow = await makeFlowModel(state, service.provideFlow)
|
|
79
|
+
service.resolvePair().resolve(true)
|
|
80
|
+
} catch (e) {
|
|
81
|
+
service.flow = null
|
|
82
|
+
service.resolvePair().reject(ResilientError.ensure(e as Error))
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return service
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const appendFlowService = <
|
|
90
|
+
C extends ClientConfig, T extends ClientContext<C>
|
|
91
|
+
>(ctx: T, alias: string = DEFAULT_ALIAS): T => {
|
|
92
|
+
const service = makeFlowService(alias)
|
|
93
|
+
|
|
94
|
+
ctx.registerService(service)
|
|
95
|
+
|
|
96
|
+
appendClientResource<C, T>(ctx, FLOW_STATE)
|
|
97
|
+
|
|
98
|
+
return ctx
|
|
99
|
+
}
|
package/src/types.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"../tsconfig.default.json",
|
|
4
|
+
"../tsconfig.react.json"
|
|
5
|
+
],
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
"rootDir": "./src/", /* Specify the root folder within your source files. */
|
|
8
|
+
"outDir": "./build/", /* Specify an output folder for all emitted files. */
|
|
9
|
+
"moduleResolution": "Bundler"
|
|
10
|
+
},
|
|
11
|
+
"exclude": [
|
|
12
|
+
"./dist/**/*",
|
|
13
|
+
"./build/**/*",
|
|
14
|
+
"./*.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/consts.ts","./src/helper.ts","./src/index.ts","./src/service.ts","./src/types.ts"],"version":"5.6.3"}
|