@crownpeak/dqm-react-component 1.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 (87) hide show
  1. package/AUTHENTICATION.md +281 -0
  2. package/BACKEND-API.md +1829 -0
  3. package/CHANGELOG.md +28 -0
  4. package/DEVELOPMENT.md +339 -0
  5. package/EXAMPLES.md +194 -0
  6. package/LICENSE +22 -0
  7. package/QUICKSTART.md +200 -0
  8. package/README.md +213 -0
  9. package/dist/DQMSidebar.d.ts +5 -0
  10. package/dist/DQMSidebar.d.ts.map +1 -0
  11. package/dist/ErrorBoundary.d.ts +34 -0
  12. package/dist/ErrorBoundary.d.ts.map +1 -0
  13. package/dist/auth-ui/assets/index-CehNKFGj.js +158 -0
  14. package/dist/auth-ui/index.html +30 -0
  15. package/dist/components/auth/DQMLogin.d.ts +16 -0
  16. package/dist/components/auth/DQMLogin.d.ts.map +1 -0
  17. package/dist/components/auth/OAuth2CallbackHandler.d.ts +15 -0
  18. package/dist/components/auth/OAuth2CallbackHandler.d.ts.map +1 -0
  19. package/dist/components/auth/index.d.ts +3 -0
  20. package/dist/components/auth/index.d.ts.map +1 -0
  21. package/dist/components/cards/CategoryCard.d.ts +2 -0
  22. package/dist/components/cards/CategoryCard.d.ts.map +1 -0
  23. package/dist/components/cards/FailedCheckpointsCard.d.ts +2 -0
  24. package/dist/components/cards/FailedCheckpointsCard.d.ts.map +1 -0
  25. package/dist/components/cards/QualityOverviewCard.d.ts +2 -0
  26. package/dist/components/cards/QualityOverviewCard.d.ts.map +1 -0
  27. package/dist/components/cards/index.d.ts +4 -0
  28. package/dist/components/cards/index.d.ts.map +1 -0
  29. package/dist/components/common/CircularProgressWithLabel.d.ts +5 -0
  30. package/dist/components/common/CircularProgressWithLabel.d.ts.map +1 -0
  31. package/dist/components/common/index.d.ts +2 -0
  32. package/dist/components/common/index.d.ts.map +1 -0
  33. package/dist/components/renderers/BrowserViewRenderer.d.ts +9 -0
  34. package/dist/components/renderers/BrowserViewRenderer.d.ts.map +1 -0
  35. package/dist/components/renderers/SafeParsedHtml.d.ts +4 -0
  36. package/dist/components/renderers/SafeParsedHtml.d.ts.map +1 -0
  37. package/dist/components/renderers/ShadowDOMRenderer.d.ts +11 -0
  38. package/dist/components/renderers/ShadowDOMRenderer.d.ts.map +1 -0
  39. package/dist/components/renderers/index.d.ts +4 -0
  40. package/dist/components/renderers/index.d.ts.map +1 -0
  41. package/dist/components/sidebar/SidebarContent.d.ts +2 -0
  42. package/dist/components/sidebar/SidebarContent.d.ts.map +1 -0
  43. package/dist/components/sidebar/SidebarFooter.d.ts +5 -0
  44. package/dist/components/sidebar/SidebarFooter.d.ts.map +1 -0
  45. package/dist/components/sidebar/SidebarHeader.d.ts +2 -0
  46. package/dist/components/sidebar/SidebarHeader.d.ts.map +1 -0
  47. package/dist/components/sidebar/SidebarSkeleton.d.ts +5 -0
  48. package/dist/components/sidebar/SidebarSkeleton.d.ts.map +1 -0
  49. package/dist/components/sidebar/StyledDrawer.d.ts +2 -0
  50. package/dist/components/sidebar/StyledDrawer.d.ts.map +1 -0
  51. package/dist/components/sidebar/StyledFab.d.ts +4 -0
  52. package/dist/components/sidebar/StyledFab.d.ts.map +1 -0
  53. package/dist/components/sidebar/index.d.ts +7 -0
  54. package/dist/components/sidebar/index.d.ts.map +1 -0
  55. package/dist/index.cjs +113 -0
  56. package/dist/index.cjs.map +1 -0
  57. package/dist/index.d.ts +4 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +13712 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/server/config.js +21 -0
  62. package/dist/server/config.js.map +1 -0
  63. package/dist/server/index.js +74 -0
  64. package/dist/server/index.js.map +1 -0
  65. package/dist/server/middleware/authenticate.js +31 -0
  66. package/dist/server/middleware/authenticate.js.map +1 -0
  67. package/dist/server/middleware/errorHandler.js +8 -0
  68. package/dist/server/middleware/errorHandler.js.map +1 -0
  69. package/dist/server/routes/auth.js +142 -0
  70. package/dist/server/routes/auth.js.map +1 -0
  71. package/dist/server/routes/dqm.js +138 -0
  72. package/dist/server/routes/dqm.js.map +1 -0
  73. package/dist/server/services/dqmClient.js +127 -0
  74. package/dist/server/services/dqmClient.js.map +1 -0
  75. package/dist/server/services/sessionStore.js +250 -0
  76. package/dist/server/services/sessionStore.js.map +1 -0
  77. package/dist/server/types.js +2 -0
  78. package/dist/server/types.js.map +1 -0
  79. package/dist/types.d.ts +76 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/utils/colors/GenerateCategoryColors.d.ts +12 -0
  82. package/dist/utils/colors/GenerateCategoryColors.d.ts.map +1 -0
  83. package/dist/utils/localStorage.d.ts +4 -0
  84. package/dist/utils/localStorage.d.ts.map +1 -0
  85. package/dist/utils/storage.d.ts +28 -0
  86. package/dist/utils/storage.d.ts.map +1 -0
  87. package/package.json +124 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Crownpeak DQM React Component - Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2025-10-30
9
+
10
+ ### Added
11
+ - Initial release of @crownpeak/dqm-react-component
12
+ - DQMSidebar component for quality analysis
13
+ - ErrorBoundary component for error handling
14
+ - Full TypeScript support with type definitions
15
+ - Material-UI integration
16
+ - Real-time quality analysis dashboard
17
+ - Browser and source view for error highlighting
18
+ - Category-based filtering
19
+ - Accessibility compliance checking
20
+ - Comprehensive documentation
21
+
22
+ ### Features
23
+ - Quality scoring and checkpoint validation
24
+ - WCAG 2.1 compliance checking
25
+ - Visual error highlighting
26
+ - Mobile responsive design
27
+ - Export functionality for highlighted errors
28
+
package/DEVELOPMENT.md ADDED
@@ -0,0 +1,339 @@
1
+ # Development Guide
2
+
3
+ ## Running the Complete Stack
4
+
5
+ ### Start Development (Frontend + Backend)
6
+
7
+ ```bash
8
+ npm run dev
9
+ ```
10
+
11
+ This runs both servers concurrently:
12
+ - **Frontend (Vite)**: http://localhost:5173
13
+ - **Backend (Express)**: http://localhost:3001
14
+
15
+ ### Start Individual Servers
16
+
17
+ ```bash
18
+ # Frontend only
19
+ npm run dev:client
20
+
21
+ # Backend only
22
+ npm run dev:server
23
+ ```
24
+
25
+ ## Project Structure
26
+
27
+ ```
28
+ crownpeak-dqm-react-component/
29
+ ├── src/ # React component source
30
+ │ ├── components/
31
+ │ │ ├── auth/ # Authentication components
32
+ │ │ ├── sidebar/ # Sidebar UI components
33
+ │ │ ├── cards/ # Analysis result cards
34
+ │ │ ├── renderers/ # HTML rendering
35
+ │ │ └── common/ # Shared components
36
+ │ ├── utils/ # Utilities
37
+ │ ├── types.ts # TypeScript types
38
+ │ ├── DQMSidebar.tsx # Main component
39
+ │ ├── index.ts # Library entry point
40
+ │ ├── App.tsx # Dev test harness
41
+ │ └── main.tsx # Dev app entry
42
+
43
+ ├── server/ # Backend API
44
+ │ ├── routes/ # API endpoints
45
+ │ │ ├── auth.ts # Authentication
46
+ │ │ └── dqm.ts # DQM proxy
47
+ │ ├── services/ # Business logic
48
+ │ │ ├── sessionStore.ts # Session management
49
+ │ │ └── dqmClient.ts # DQM API client
50
+ │ ├── middleware/ # Express middleware
51
+ │ ├── config.ts # Configuration
52
+ │ ├── types.ts # TypeScript types
53
+ │ └── index.ts # Express app
54
+
55
+ ├── dist/ # Build output
56
+ │ ├── index.js # Library ESM
57
+ │ ├── index.cjs # Library CommonJS
58
+ │ ├── index.d.ts # Type declarations
59
+ │ └── server/ # Compiled server code
60
+
61
+ ├── vite.config.ts # Vite configuration
62
+ ├── tsconfig.json # TypeScript base config
63
+ ├── tsconfig.lib.json # Library build config
64
+ ├── tsconfig.server.json # Server build config
65
+ └── package.json # Dependencies & scripts
66
+ ```
67
+
68
+ ## Authentication Modes
69
+
70
+ The component supports two modes:
71
+
72
+ ### 1. Backend Mode (Recommended for Production)
73
+
74
+ ```tsx
75
+ <DQMSidebar
76
+ config={{
77
+ authBackendUrl: 'http://localhost:3001',
78
+ useLocalStorage: true,
79
+ }}
80
+ />
81
+ ```
82
+
83
+ - All API calls proxied through backend
84
+ - Session token stored in localStorage
85
+ - Real credentials never exposed to client
86
+
87
+ ### 2. Direct Mode (Development/Staging)
88
+
89
+ ```tsx
90
+ <DQMSidebar
91
+ config={{
92
+ apiKey: 'YOUR_API_KEY',
93
+ websiteId: 'YOUR_WEBSITE_ID',
94
+ }}
95
+ />
96
+ ```
97
+
98
+ - Direct communication with Crownpeak DQM API
99
+ - Credentials in props or localStorage
100
+ - No backend required
101
+
102
+ ## Testing Both Modes
103
+
104
+ ### Test Backend Mode
105
+
106
+ 1. Start both servers: `npm run dev`
107
+ 2. Open http://localhost:5173
108
+ 3. Click "Login" and enter credentials
109
+ 4. Backend validates and issues session token
110
+ 5. All API calls go through http://localhost:3001
111
+
112
+ ### Test Direct Mode
113
+
114
+ 1. Update `src/App.tsx`:
115
+ ```tsx
116
+ <DQMSidebar
117
+ config={{
118
+ apiKey: 'YOUR_API_KEY',
119
+ websiteId: 'YOUR_WEBSITE_ID',
120
+ }}
121
+ />
122
+ ```
123
+
124
+ 2. Start frontend only: `npm run dev:client`
125
+ 3. No backend needed, direct API calls
126
+
127
+ ## Building for Production
128
+
129
+ ### Build Library
130
+
131
+ ```bash
132
+ npm run build:lib
133
+ ```
134
+
135
+ Output: `dist/index.{js,cjs,d.ts}`
136
+
137
+ ### Build Server
138
+
139
+ ```bash
140
+ npm run build:server
141
+ ```
142
+
143
+ Output: `dist/server/`
144
+
145
+ ### Test Library Build
146
+
147
+ ```bash
148
+ npm pack
149
+ # Creates: crownpeak-dqm-react-component-1.0.0.tgz
150
+ ```
151
+
152
+ ## Environment Configuration
153
+
154
+ ### Frontend (.env)
155
+
156
+ ```env
157
+ VITE_BACKEND_URL=http://localhost:3001
158
+ ```
159
+
160
+ ### Backend (.env)
161
+
162
+ ```env
163
+ PORT=3001
164
+ CORS_ORIGINS=http://localhost:5173,http://localhost:3000
165
+ DQM_API_BASE_URL=https://api.crownpeak.net/dqm-cms/v1
166
+ JWT_SECRET=your-secret-key
167
+ ```
168
+
169
+ ## API Testing with cURL
170
+
171
+ ### Login
172
+
173
+ ```bash
174
+ curl -X POST http://localhost:3001/auth/login \
175
+ -H "Content-Type: application/json" \
176
+ -d '{
177
+ "apiKey": "YOUR_API_KEY",
178
+ "websiteId": "YOUR_WEBSITE_ID"
179
+ }'
180
+ ```
181
+
182
+ Response:
183
+ ```json
184
+ {
185
+ "sessionToken": "abc123...",
186
+ "websiteId": "YOUR_WEBSITE_ID"
187
+ }
188
+ ```
189
+
190
+ ### Start Analysis
191
+
192
+ ```bash
193
+ curl -X POST http://localhost:3001/dqm/assets \
194
+ -H "Authorization: Bearer YOUR_SESSION_TOKEN" \
195
+ -H "Content-Type: application/json" \
196
+ -d '{
197
+ "html": "<html><body><h1>Test</h1></body></html>",
198
+ "url": "https://example.com"
199
+ }'
200
+ ```
201
+
202
+ ### Get Results
203
+
204
+ ```bash
205
+ curl -X GET http://localhost:3001/dqm/assets/ASSET_ID \
206
+ -H "Authorization: Bearer YOUR_SESSION_TOKEN"
207
+ ```
208
+
209
+ ## Debugging
210
+
211
+ ### Enable Verbose Logging
212
+
213
+ Backend logs all requests automatically. Check terminal output:
214
+
215
+ ```
216
+ [2025-10-31T10:00:00.000Z] POST /auth/login
217
+ [Auth] User logged in successfully (websiteId: test_id)
218
+ [SessionStore] Created session: abc12345... (expires in 1440 minutes)
219
+ ```
220
+
221
+ ### Check Session Status
222
+
223
+ ```bash
224
+ curl -X GET http://localhost:3001/auth/session \
225
+ -H "Authorization: Bearer YOUR_SESSION_TOKEN"
226
+ ```
227
+
228
+ ### View Active Sessions (Dev Only)
229
+
230
+ Add to `server/routes/auth.ts`:
231
+
232
+ ```typescript
233
+ authRouter.get('/sessions/count', (req, res) => {
234
+ res.json({ count: sessionStore.count() });
235
+ });
236
+ ```
237
+
238
+ ## Common Issues
239
+
240
+ ### CORS Errors
241
+
242
+ **Problem:** Frontend can't reach backend
243
+
244
+ **Solution:** Add frontend URL to `CORS_ORIGINS` in `.env`:
245
+ ```env
246
+ CORS_ORIGINS=http://localhost:5173,http://localhost:3000
247
+ ```
248
+
249
+ ### Port Already in Use
250
+
251
+ **Problem:** `Error: listen EADDRINUSE: address already in use :::3001`
252
+
253
+ **Solution:** Kill process or change port:
254
+ ```bash
255
+ lsof -ti:3001 | xargs kill -9
256
+ # or change PORT in .env
257
+ ```
258
+
259
+ ### Session Expired
260
+
261
+ **Problem:** 401 Unauthorized after some time
262
+
263
+ **Solution:** Session TTL is 24 hours. Login again or extend TTL in `server/config.ts`:
264
+ ```typescript
265
+ session: {
266
+ ttl: 48 * 60 * 60 * 1000, // 48 hours
267
+ }
268
+ ```
269
+
270
+ ### TypeScript Errors
271
+
272
+ **Problem:** Type errors in server code
273
+
274
+ **Solution:** Ensure @types packages installed:
275
+ ```bash
276
+ npm install --save-dev @types/express @types/cors @types/node
277
+ ```
278
+
279
+ ## Production Deployment
280
+
281
+ ### Deploy Backend
282
+
283
+ 1. Build server: `npm run build:server`
284
+ 2. Copy `dist/server/` to production
285
+ 3. Set environment variables
286
+ 4. Install production dependencies only
287
+ 5. Start: `node dist/server/index.js`
288
+
289
+ ### Use Redis for Sessions
290
+
291
+ Replace `server/services/sessionStore.ts` with Redis:
292
+
293
+ ```typescript
294
+ import { createClient } from 'redis';
295
+
296
+ const redis = createClient({
297
+ url: process.env.REDIS_URL
298
+ });
299
+
300
+ await redis.connect();
301
+
302
+ export const sessionStore = {
303
+ create: async (apiKey, websiteId) => {
304
+ const token = generateToken();
305
+ await redis.setEx(`session:${token}`, 86400, JSON.stringify({
306
+ apiKey, websiteId, createdAt: Date.now()
307
+ }));
308
+ return token;
309
+ },
310
+ get: async (token) => {
311
+ const data = await redis.get(`session:${token}`);
312
+ return data ? JSON.parse(data) : null;
313
+ },
314
+ delete: async (token) => {
315
+ await redis.del(`session:${token}`);
316
+ }
317
+ };
318
+ ```
319
+
320
+ ### Add Rate Limiting
321
+
322
+ ```bash
323
+ npm install express-rate-limit
324
+ ```
325
+
326
+ ```typescript
327
+ import rateLimit from 'express-rate-limit';
328
+
329
+ const limiter = rateLimit({
330
+ windowMs: 15 * 60 * 1000, // 15 minutes
331
+ max: 100 // limit each IP to 100 requests per windowMs
332
+ });
333
+
334
+ app.use('/auth/login', limiter);
335
+ ```
336
+
337
+ ## License
338
+
339
+ MIT
package/EXAMPLES.md ADDED
@@ -0,0 +1,194 @@
1
+ # Example Usage
2
+
3
+ This directory contains example implementations of the `@crownpeak/dqm-react-component`.
4
+
5
+ ## Basic React App Example
6
+
7
+ ```tsx
8
+ import React, {useState} from 'react';
9
+ import {DQMSidebar} from '@crownpeak/dqm-react-component';
10
+
11
+ function App() {
12
+ const [sidebarOpen, setSidebarOpen] = useState(false);
13
+
14
+ // Set DQM credentials (e.g., from environment variables)
15
+ React.useEffect(() => {
16
+ localStorage.setItem('dqm_apiKey', process.env.REACT_APP_DQM_API_KEY);
17
+ localStorage.setItem('dqm_websiteID', process.env.REACT_APP_DQM_WEBSITE_ID);
18
+ }, []);
19
+
20
+ return (
21
+ <div>
22
+ <h1>My Application with DQM</h1>
23
+ <p>This page is monitored by Crownpeak DQM for quality assurance.</p>
24
+
25
+ <DQMSidebar
26
+ open={sidebarOpen}
27
+ onClose={() => setSidebarOpen(false)}
28
+ onOpen={() => setSidebarOpen(true)}
29
+ />
30
+ </div>
31
+ );
32
+ }
33
+
34
+ export default App;
35
+ ```
36
+
37
+ ## Next.js Example
38
+
39
+ ```tsx
40
+ // pages/_app.tsx
41
+ import type {AppProps} from 'next/app';
42
+ import {useEffect, useState} from 'react';
43
+ import {DQMSidebar} from '@crownpeak/dqm-react-component';
44
+ import {createTheme} from '@mui/material/styles';
45
+
46
+ const theme = createTheme();
47
+
48
+ function MyApp({Component, pageProps}: AppProps) {
49
+ const [sidebarOpen, setSidebarOpen] = useState(false);
50
+
51
+ useEffect(() => {
52
+ if (typeof window !== 'undefined') {
53
+ localStorage.setItem('dqm_apiKey', process.env.NEXT_PUBLIC_DQM_API_KEY!);
54
+ localStorage.setItem('dqm_websiteID', process.env.NEXT_PUBLIC_DQM_WEBSITE_ID!);
55
+ }
56
+ }, []);
57
+
58
+ return (
59
+ <>
60
+ <Component {...pageProps} />
61
+ <DQMSidebar
62
+ open={sidebarOpen}
63
+ onClose={() => setSidebarOpen(false)}
64
+ onOpen={() => setSidebarOpen(true)}
65
+ />
66
+ </>
67
+ );
68
+ }
69
+
70
+ export default MyApp;
71
+ ```
72
+
73
+ ## TypeScript Example with Custom Configuration
74
+
75
+ ```tsx
76
+ import React, { useState, useCallback } from 'react';
77
+ import { DQMSidebar, ErrorBoundary } from '@crownpeak/dqm-react-component';
78
+ import type { DQMSidebarProps } from '@crownpeak/dqm-react-component';
79
+
80
+ interface DQMConfig {
81
+ apiKey: string;
82
+ websiteId: string;
83
+ enabled: boolean;
84
+ }
85
+
86
+ const config: DQMConfig = {
87
+ apiKey: process.env.REACT_APP_DQM_API_KEY!,
88
+ websiteId: process.env.REACT_APP_DQM_WEBSITE_ID!,
89
+ enabled: process.env.NODE_ENV === 'production'
90
+ };
91
+
92
+ function App() {
93
+ const [sidebarOpen, setSidebarOpen] = useState(false);
94
+
95
+ React.useEffect(() => {
96
+ if (config.enabled) {
97
+ localStorage.setItem('dqm_apiKey', config.apiKey);
98
+ localStorage.setItem('dqm_websiteID', config.websiteId);
99
+ }
100
+ }, []);
101
+
102
+ const handleOpen = useCallback(() => {
103
+ setSidebarOpen(true);
104
+ }, []);
105
+
106
+ const handleClose = useCallback(() => {
107
+ setSidebarOpen(false);
108
+ }, []);
109
+
110
+ if (!config.enabled) {
111
+ return <div>DQM is disabled in this environment</div>;
112
+ }
113
+
114
+ return (
115
+ <ErrorBoundary>
116
+ <div>
117
+ <h1>App with DQM Quality Monitoring</h1>
118
+ <DQMSidebar
119
+ open={sidebarOpen}
120
+ onClose={handleClose}
121
+ onOpen={handleOpen}
122
+ />
123
+ </div>
124
+ </ErrorBoundary>
125
+ );
126
+ }
127
+
128
+ export default App;
129
+ ```
130
+
131
+ ## Conditional Rendering (Development Only)
132
+
133
+ ```tsx
134
+ import React from 'react';
135
+ import { DQMSidebar } from '@crownpeak/dqm-react-component';
136
+
137
+ function App() {
138
+ const [sidebarOpen, setSidebarOpen] = React.useState(false);
139
+ const isDevelopment = process.env.NODE_ENV === 'development';
140
+
141
+ return (
142
+ <div>
143
+ <h1>My App</h1>
144
+
145
+ {/* Only show DQM in development */}
146
+ {isDevelopment && (
147
+ <DQMSidebar
148
+ open={sidebarOpen}
149
+ onClose={() => setSidebarOpen(false)}
150
+ onOpen={() => setSidebarOpen(true)}
151
+ />
152
+ )}
153
+ </div>
154
+ );
155
+ }
156
+ ```
157
+
158
+ ## With Custom Button Trigger
159
+
160
+ ```tsx
161
+ import React, { useState } from 'react';
162
+ import { DQMSidebar } from '@crownpeak/dqm-react-component';
163
+ import { Button } from '@mui/material';
164
+ import { Assessment } from '@mui/icons-material';
165
+
166
+ function App() {
167
+ const [sidebarOpen, setSidebarOpen] = useState(false);
168
+
169
+ return (
170
+ <div>
171
+ <header>
172
+ <Button
173
+ variant="outlined"
174
+ startIcon={<Assessment />}
175
+ onClick={() => setSidebarOpen(true)}
176
+ >
177
+ Quality Check
178
+ </Button>
179
+ </header>
180
+
181
+ <main>
182
+ <h1>Content</h1>
183
+ </main>
184
+
185
+ <DQMSidebar
186
+ open={sidebarOpen}
187
+ onClose={() => setSidebarOpen(false)}
188
+ onOpen={() => setSidebarOpen(true)}
189
+ />
190
+ </div>
191
+ );
192
+ }
193
+ ```
194
+
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Crownpeak Technology GmbH
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.
22
+