@learncard/open-badge-v2-plugin 1.1.25 → 1.1.27
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/README.md +45 -40
- package/package.json +59 -57
- package/src/index.ts +2 -0
- package/src/plugin.ts +164 -0
- package/src/types.ts +23 -0
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -2,38 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
Wrap legacy Open Badges v2.0 (OBv2) assertions into self‑issued W3C Verifiable Credentials (VCs) so they can be stored, indexed, and used within the LearnCard ecosystem.
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
5
|
+
- Name: `OpenBadgeV2`
|
|
6
|
+
- Method: `wrapOpenBadgeV2(obv2Assertion: object | string): Promise<VC>`
|
|
7
|
+
- Context added: `https://ctx.learncard.com/wrappers/obv2/1.0.0.json`
|
|
8
|
+
- VC type produced: `LegacyOpenBadgeCredential`
|
|
9
9
|
|
|
10
10
|
## Why this plugin?
|
|
11
11
|
|
|
12
12
|
Many issuers still publish Open Badges v2.0 assertions (JSON), not VCs. This plugin provides a backwards‑compatibility bridge by wrapping the raw OBv2 assertion inside a signed VC you control, preserving the original badge payload under `legacyAssertion`.
|
|
13
13
|
|
|
14
14
|
This enables:
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
15
|
+
|
|
16
|
+
- Use of legacy badges anywhere a VC is expected (storage, indexing, sharing).
|
|
17
|
+
- A stable LearnCard vocabulary for the wrapper (`LegacyOpenBadgeCredential`).
|
|
18
|
+
- Consistent DID‑based signing and verification workflows.
|
|
18
19
|
|
|
19
20
|
## What it produces
|
|
20
21
|
|
|
21
22
|
A signed VC with these characteristics:
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
23
|
+
|
|
24
|
+
- `@context`: `["https://www.w3.org/ns/credentials/v2", "https://ctx.learncard.com/wrappers/obv2/1.0.0.json"]`
|
|
25
|
+
- `type`: `["VerifiableCredential", "LegacyOpenBadgeCredential"]`
|
|
26
|
+
- `issuer`: your LearnCard DID
|
|
27
|
+
- `validFrom`: `new Date().toISOString()`
|
|
28
|
+
- `credentialSubject.id`: your LearnCard DID
|
|
29
|
+
- `legacyAssertion`: the original OBv2 assertion JSON
|
|
28
30
|
|
|
29
31
|
Notes:
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
+
|
|
33
|
+
- The wrapper signs the fact that “you attest to this legacy assertion,” not that you were the original OBv2 issuer.
|
|
34
|
+
- Minimal OBv2 validation is performed: `id` (string), `type` includes `"Assertion"`, `issuedOn` (string).
|
|
32
35
|
|
|
33
36
|
## Requirements
|
|
34
37
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
38
|
+
- A LearnCard instance with the VC plugin installed (used to sign the VC).
|
|
39
|
+
- Fetch available for remote inputs (Node 18+ or a polyfill like `undici`).
|
|
37
40
|
|
|
38
41
|
## Installation
|
|
39
42
|
|
|
@@ -41,7 +44,7 @@ This package is part of the monorepo. In external usage, install from npm (when
|
|
|
41
44
|
|
|
42
45
|
```bash
|
|
43
46
|
# inside the monorepo
|
|
44
|
-
|
|
47
|
+
bunx nx run open-badge-v2-plugin:build
|
|
45
48
|
```
|
|
46
49
|
|
|
47
50
|
## Usage
|
|
@@ -80,13 +83,13 @@ const vc = await learnCard.invoke.wrapOpenBadgeV2(ipfsUrl);
|
|
|
80
83
|
|
|
81
84
|
```ts
|
|
82
85
|
const obv2 = {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
'@context': 'https://w3id.org/openbadges/v2',
|
|
87
|
+
type: 'Assertion',
|
|
88
|
+
id: 'https://issuer.example.org/assertions/abc',
|
|
89
|
+
issuedOn: '2024-05-02T12:34:56Z',
|
|
90
|
+
recipient: { type: 'email', identity: 'user@example.org', hashed: false },
|
|
91
|
+
badge: 'https://issuer.example.org/badges/my-badge',
|
|
92
|
+
// ... any other OBv2 fields ...
|
|
90
93
|
};
|
|
91
94
|
|
|
92
95
|
const vc = await learnCard.invoke.wrapOpenBadgeV2(obv2);
|
|
@@ -105,36 +108,38 @@ const vc = await learnCard.invoke.wrapOpenBadgeV2(obv2String);
|
|
|
105
108
|
wrapOpenBadgeV2(obv2Assertion: object | string): Promise<VC>
|
|
106
109
|
```
|
|
107
110
|
|
|
108
|
-
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
- `obv2Assertion` may be:
|
|
112
|
+
- A raw OBv2 assertion object
|
|
113
|
+
- A URL string (http(s)://, ipfs://, ipns://) pointing to the assertion JSON
|
|
114
|
+
- A JSON string containing the assertion
|
|
112
115
|
|
|
113
116
|
Validation rules enforced:
|
|
114
|
-
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
+
|
|
118
|
+
- `assertion.id` must be a non‑empty string
|
|
119
|
+
- `assertion.type` must include `"Assertion"`
|
|
120
|
+
- `assertion.issuedOn` must be a string
|
|
117
121
|
|
|
118
122
|
## JSON‑LD Context
|
|
119
123
|
|
|
120
124
|
The wrapper vocabulary is published at:
|
|
121
125
|
|
|
122
|
-
-
|
|
126
|
+
- `https://ctx.learncard.com/wrappers/obv2/1.0.0.json`
|
|
123
127
|
|
|
124
128
|
It defines:
|
|
125
|
-
|
|
126
|
-
-
|
|
129
|
+
|
|
130
|
+
- `LegacyOpenBadgeCredential`
|
|
131
|
+
- `legacyAssertion` as `@type: @json` for embedding raw OBv2 JSON
|
|
127
132
|
|
|
128
133
|
## Development
|
|
129
134
|
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
135
|
+
- Build: `bunx nx run open-badge-v2-plugin:build`
|
|
136
|
+
- Source: `packages/plugins/open-badge-v2/src/`
|
|
137
|
+
- Context file: `packages/learn-card-contexts/wrappers/obv2/1.0.0.json`
|
|
133
138
|
|
|
134
139
|
## Caveats
|
|
135
140
|
|
|
136
|
-
-
|
|
137
|
-
-
|
|
141
|
+
- Wrapping does not retroactively convert OBv2 into a first‑party VC issued by the original badge issuer; it produces a VC signed by your DID that embeds the legacy assertion.
|
|
142
|
+
- Always fetch and wrap assertions from trusted sources.
|
|
138
143
|
|
|
139
144
|
## License
|
|
140
145
|
|
package/package.json
CHANGED
|
@@ -1,58 +1,60 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
2
|
+
"name": "@learncard/open-badge-v2-plugin",
|
|
3
|
+
"version": "1.1.27",
|
|
4
|
+
"description": "OpenBadge v2 wrapper plugin for LearnCard. Wraps OBv2 assertions into self-issued VCs.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/open-badge-v2-plugin.esm.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"development": "./src/index.ts",
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/open-badge-v2-plugin.esm.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"src"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "node ./scripts/build.mjs && shx cp ./scripts/mixedEntypoint.js ./dist/index.cjs && tsc --p tsconfig.json && shx cp ./dist/index.d.ts ./dist/index.d.cts",
|
|
27
|
+
"test": "jest --passWithNoTests",
|
|
28
|
+
"test:watch": "jest --watch",
|
|
29
|
+
"test:coverage": "jest --silent --ci --coverage --coverageReporters=\"text\" --coverageReporters=\"text-summary\""
|
|
30
|
+
},
|
|
31
|
+
"author": "Learning Economy Foundation (www.learningeconomy.io)",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"homepage": "https://github.com/learningeconomy/LearnCard/tree/main/packages/plugins/open-badge-v2/README.md",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/learningeconomy/LearnCard"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/learningeconomy/LearnCard/issues"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/jest": "^29.2.2",
|
|
43
|
+
"@types/node": "^17.0.31",
|
|
44
|
+
"aqu": "0.4.3",
|
|
45
|
+
"esbuild": "^0.27.1",
|
|
46
|
+
"esbuild-jest": "^0.5.0",
|
|
47
|
+
"esbuild-plugin-copy": "^1.3.0",
|
|
48
|
+
"jest": "^29.3.0",
|
|
49
|
+
"shx": "^0.3.4",
|
|
50
|
+
"ts-jest": "^29.0.3"
|
|
51
|
+
},
|
|
52
|
+
"types": "./dist/index.d.ts",
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@learncard/core": "9.4.26",
|
|
55
|
+
"@learncard/crypto-plugin": "1.1.26",
|
|
56
|
+
"@learncard/vc-plugin": "1.5.6",
|
|
57
|
+
"@learncard/types": "5.17.5"
|
|
58
|
+
},
|
|
59
|
+
"sideEffects": false
|
|
60
|
+
}
|
package/src/index.ts
ADDED
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { LearnCard } from '@learncard/core';
|
|
2
|
+
import { UnsignedVC, VC } from '@learncard/types';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
OpenBadgeV2Plugin,
|
|
6
|
+
OBV2_WRAPPER_CONTEXT_URL,
|
|
7
|
+
OpenBadgeV2PluginDependentMethods,
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
const VC_V2_CONTEXT = 'https://www.w3.org/ns/credentials/v2';
|
|
11
|
+
|
|
12
|
+
const isUrl = (value: string): boolean =>
|
|
13
|
+
/^(https?:)?\/\//i.test(value) || value.startsWith('ipfs://') || value.startsWith('ipns://');
|
|
14
|
+
|
|
15
|
+
const isValidAndSafeUrl = (url: string): void => {
|
|
16
|
+
try {
|
|
17
|
+
const parsedUrl = new URL(url);
|
|
18
|
+
|
|
19
|
+
// Only allow http, https, ipfs, ipns protocols
|
|
20
|
+
const allowedProtocols = ['https:', 'http:', 'ipfs:', 'ipns:'];
|
|
21
|
+
if (!allowedProtocols.includes(parsedUrl.protocol)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`wrapOpenBadgeV2: Protocol '${parsedUrl.protocol}' is not allowed. Only HTTP(S), IPFS, and IPNS are supported`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Skip internal network checks for IPFS/IPNS as they don't use traditional IP addresses
|
|
28
|
+
if (parsedUrl.protocol === 'ipfs:' || parsedUrl.protocol === 'ipns:') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Block localhost and internal networks for HTTP(S)
|
|
33
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
34
|
+
|
|
35
|
+
// Block localhost variations
|
|
36
|
+
if (
|
|
37
|
+
hostname === 'localhost' ||
|
|
38
|
+
hostname === '127.0.0.1' ||
|
|
39
|
+
hostname === '0.0.0.0' ||
|
|
40
|
+
hostname === '::1'
|
|
41
|
+
) {
|
|
42
|
+
throw new Error('wrapOpenBadgeV2: Access to localhost is not allowed');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Block private IP ranges (RFC 1918)
|
|
46
|
+
if (
|
|
47
|
+
hostname.startsWith('10.') ||
|
|
48
|
+
hostname.startsWith('192.168.') ||
|
|
49
|
+
(hostname.startsWith('172.') && /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname))
|
|
50
|
+
) {
|
|
51
|
+
throw new Error('wrapOpenBadgeV2: Access to private IP ranges is not allowed');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Block link-local addresses
|
|
55
|
+
if (hostname.startsWith('169.254.')) {
|
|
56
|
+
throw new Error('wrapOpenBadgeV2: Access to link-local addresses is not allowed');
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error instanceof TypeError) {
|
|
60
|
+
throw new Error('wrapOpenBadgeV2: Invalid URL format');
|
|
61
|
+
}
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
async function getObv2Assertion(input: object | string): Promise<Record<string, any>> {
|
|
67
|
+
if (typeof input === 'string') {
|
|
68
|
+
// Treat string as URL. If it looks like raw JSON, try to parse for developer ergonomics
|
|
69
|
+
const trimmed = input.trim();
|
|
70
|
+
if (!isUrl(trimmed) && (trimmed.startsWith('{') || trimmed.startsWith('['))) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(trimmed);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
throw new Error('wrapOpenBadgeV2: Provided string is not a valid URL or JSON');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Validate URL for security
|
|
79
|
+
isValidAndSafeUrl(trimmed);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Add timeout to prevent hanging requests (10 seconds)
|
|
83
|
+
const controller = new AbortController();
|
|
84
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
|
85
|
+
|
|
86
|
+
const res = await fetch(trimmed, {
|
|
87
|
+
signal: controller.signal,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
clearTimeout(timeoutId);
|
|
91
|
+
|
|
92
|
+
if (!res.ok)
|
|
93
|
+
throw new Error(`wrapOpenBadgeV2: Failed to fetch OBv2 assertion: ${res.status}`);
|
|
94
|
+
|
|
95
|
+
return await res.json();
|
|
96
|
+
} catch (error: any) {
|
|
97
|
+
if (error instanceof TypeError) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`wrapOpenBadgeV2: Network error while fetching OBv2 assertion: ${error.message}`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (error?.name === 'AbortError') {
|
|
103
|
+
throw new Error('wrapOpenBadgeV2: Request timed out after 10 seconds');
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Add basic type validation before assertion
|
|
110
|
+
if (typeof input !== 'object' || input === null) {
|
|
111
|
+
throw new Error('wrapOpenBadgeV2: Input must be a valid object');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return input as Record<string, any>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function validateObv2(obv2: Record<string, any>): void {
|
|
118
|
+
if (!obv2 || typeof obv2 !== 'object') throw new Error('wrapOpenBadgeV2: Missing assertion');
|
|
119
|
+
|
|
120
|
+
const id = obv2.id;
|
|
121
|
+
if (typeof id !== 'string' || !id.length)
|
|
122
|
+
throw new Error('wrapOpenBadgeV2: assertion.id is required');
|
|
123
|
+
|
|
124
|
+
const t = obv2.type;
|
|
125
|
+
const hasAssertionType =
|
|
126
|
+
(typeof t === 'string' && t === 'Assertion') ||
|
|
127
|
+
(Array.isArray(t) && t.includes('Assertion'));
|
|
128
|
+
if (!hasAssertionType)
|
|
129
|
+
throw new Error("wrapOpenBadgeV2: assertion.type must include 'Assertion'");
|
|
130
|
+
|
|
131
|
+
if (!obv2.issuedOn || typeof obv2.issuedOn !== 'string') {
|
|
132
|
+
throw new Error('wrapOpenBadgeV2: assertion.issuedOn (string) is required');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const openBadgeV2Plugin = (
|
|
137
|
+
learnCard: LearnCard<any, 'id', OpenBadgeV2PluginDependentMethods>
|
|
138
|
+
): OpenBadgeV2Plugin => ({
|
|
139
|
+
name: 'OpenBadgeV2',
|
|
140
|
+
displayName: 'OpenBadge v2 Wrapper',
|
|
141
|
+
description: 'Wrap legacy OpenBadge v2.0 assertions into self-issued Verifiable Credentials',
|
|
142
|
+
methods: {
|
|
143
|
+
wrapOpenBadgeV2: async (_learnCard, obv2Assertion: object | string): Promise<VC> => {
|
|
144
|
+
const issuerDid = learnCard.id.did();
|
|
145
|
+
|
|
146
|
+
const obv2 = await getObv2Assertion(obv2Assertion);
|
|
147
|
+
validateObv2(obv2);
|
|
148
|
+
|
|
149
|
+
const unsigned: UnsignedVC = {
|
|
150
|
+
'@context': [VC_V2_CONTEXT, OBV2_WRAPPER_CONTEXT_URL],
|
|
151
|
+
id: `urn:uuid:${_learnCard.invoke.crypto().randomUUID()}`,
|
|
152
|
+
type: ['VerifiableCredential', 'LegacyOpenBadgeCredential'],
|
|
153
|
+
issuer: issuerDid,
|
|
154
|
+
validFrom: new Date().toISOString(),
|
|
155
|
+
credentialSubject: {
|
|
156
|
+
id: issuerDid,
|
|
157
|
+
},
|
|
158
|
+
legacyAssertion: obv2,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return learnCard.invoke.issueCredential(unsigned, { proofPurpose: 'assertionMethod' });
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Plugin } from '@learncard/core';
|
|
2
|
+
import { VC } from '@learncard/types';
|
|
3
|
+
import { VCPluginMethods } from '@learncard/vc-plugin';
|
|
4
|
+
import { CryptoPluginMethods } from '@learncard/crypto-plugin';
|
|
5
|
+
|
|
6
|
+
export type OpenBadgeV2PluginDependentMethods = VCPluginMethods & CryptoPluginMethods;
|
|
7
|
+
|
|
8
|
+
export type OpenBadgeV2PluginMethods = {
|
|
9
|
+
/**
|
|
10
|
+
* Wrap a legacy OpenBadge v2.0 assertion (object or URL) into a self-issued VC
|
|
11
|
+
*/
|
|
12
|
+
wrapOpenBadgeV2: (obv2Assertion: object | string) => Promise<VC>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type OpenBadgeV2Plugin = Plugin<
|
|
16
|
+
'OpenBadgeV2',
|
|
17
|
+
any,
|
|
18
|
+
OpenBadgeV2PluginMethods,
|
|
19
|
+
'id',
|
|
20
|
+
OpenBadgeV2PluginDependentMethods
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
export const OBV2_WRAPPER_CONTEXT_URL = 'https://ctx.learncard.com/wrappers/obv2/1.0.0.json';
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Learning Economy Foundation <sdk@learningeconomy.io>
|
|
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.
|