@zfqh/uniapp-x 0.0.7 → 0.0.9

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 CHANGED
@@ -58,4 +58,3 @@ onAppShow((options) => handleShow(options)); // 经典 uni-app 使用 onShow
58
58
 
59
59
  - 开发约定、完整 API、平台差异与验证:[AGENTS.md](AGENTS.md)
60
60
  - 示例工程:`examples/uniapp-vapor`(蒸汽模式)、`examples/uniapp-nvue`(经典 Vue3 nvue)
61
- - 变更记录:[CHANGELOG.md](CHANGELOG.md)
@@ -1,11 +1,5 @@
1
- /** 发送短信验证码 */
2
- export declare const sendSmsCode: (...args: any[]) => any;
3
- /** 获取腾讯上传临时Token */
4
- export declare const getTencentToken: (...args: any[]) => any;
5
1
  /** 获取最新版本 */
6
2
  export declare const getLastVersion: (...args: any[]) => any;
7
- /** 获取cos签名后的地址 */
8
- export declare const getCosUrls: (...args: any[]) => any;
9
3
  /** 刷新token */
10
4
  export declare const postRefreshToken: (...args: any[]) => any;
11
5
  /** 获取字典数据 */
package/lib/apis/core.js CHANGED
@@ -1,12 +1,6 @@
1
1
  import { GET, POST } from './request';
2
- /** 发送短信验证码 */
3
- export const sendSmsCode = (...args) => POST('/admin-api/system/auth/send-checkCode', ...args);
4
- /** 获取腾讯上传临时Token */
5
- export const getTencentToken = (...args) => GET('/app-api/infra/file/getTencentToken', ...args);
6
2
  /** 获取最新版本 */
7
3
  export const getLastVersion = (...args) => POST('/app-api/system/app/version/get-last-version-v2', ...args);
8
- /** 获取cos签名后的地址 */
9
- export const getCosUrls = (...args) => POST('/app-api/infra/cos/getUrls', ...args);
10
4
  /** 刷新token */
11
5
  export const postRefreshToken = (...args) => POST('/app-api/robot/store/auth/refreshToken?refreshToken=:refreshToken', ...args);
12
6
  /** 获取字典数据 */
@@ -22,9 +22,9 @@ type UploadParams = {
22
22
  };
23
23
  /** 上传配置 */
24
24
  export type UploadConfig = {
25
- /** 上传请求,例如获取Token */
25
+ /** 上传请求(获取临时凭证等),需由业务通过 defineConfig({ upload: { request } }) 配置 */
26
26
  request: (params?: UploadParams) => Promise<UploadResponceData>;
27
27
  };
28
- /** 上传配置 */
28
+ /** 上传配置:内置凭证接口已移除,未配置时调用会抛错 */
29
29
  declare const Upload: UploadConfig;
30
30
  export default Upload;
@@ -1,19 +1,7 @@
1
- import { getTencentToken } from '../apis/core';
2
- import cdn from './cdn';
3
- /** 上传配置 */
1
+ /** 上传配置:内置凭证接口已移除,未配置时调用会抛错 */
4
2
  const Upload = {
5
- /** 上传请求,默认调用 getTencentToken 获取腾讯云临时密钥 */
6
3
  request: async () => {
7
- const { data } = await getTencentToken();
8
- return {
9
- secret_id: data.tmpSecretId,
10
- secret_key: data.tmpSecretKey,
11
- session_token: data.sessionToken,
12
- create_time: data.createTime,
13
- expire_time: data.expirationTime,
14
- bucket: data.bucket,
15
- region: cdn.isAccelerate ? 'accelerate' : data.region,
16
- };
4
+ throw new Error('未配置上传凭证请求:请通过 defineConfig({ upload: { request } }) 配置 upload.request');
17
5
  },
18
6
  };
19
7
  export default Upload;
@@ -3,7 +3,7 @@ export declare function checkRefreshToken(): Promise<boolean>;
3
3
  export interface UploadFileOptions {
4
4
  key?: string;
5
5
  path?: string;
6
- /** 获取上传任务实例,可用于 task.abort() 取消上传;token 过期重试时会再次回调 */
6
+ /** 获取上传任务实例,可用于 task.abort() 取消上传;H5 宿主 webview 走 plus.uploader 时为含 abort / onProgressUpdate / offProgressUpdate 的适配对象;token 过期重试时会再次回调 */
7
7
  getTask?: ((task: UniNamespace.UploadTask) => void) | ((task: any) => void);
8
8
  }
9
9
  export declare function uploadFile(file: any, opts?: UploadFileOptions): Promise<{
package/lib/utils/cos.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Base64, Hex, HmacSHA1, SHA1, Utf8 } from 'crypto-es';
2
2
  import { guid, timeFormat } from '@zfqh/uview-x/utils/function';
3
3
  import upload from '../configs/upload';
4
+ import { IS_WEBVIEW_ENV } from './consts';
4
5
  import { cosCompress } from './index';
5
6
  const data = {};
6
7
  let promise = null;
@@ -18,81 +19,196 @@ export async function checkRefreshToken() {
18
19
  }
19
20
  return promise;
20
21
  }
22
+ /**
23
+ * 是否使用 HTML5+ Uploader:
24
+ * H5 页面被宿主 web-view 嵌入(IS_WEBVIEW_ENV)、plus.uploader 可用,且存在文件路径。
25
+ */
26
+ function canUsePlusUploader(filePath) {
27
+ let available = false;
28
+ // #ifdef WEB || H5
29
+ available =
30
+ IS_WEBVIEW_ENV && typeof filePath === 'string' && !!filePath && typeof plus !== 'undefined' && !!plus.uploader;
31
+ // #endif
32
+ return available;
33
+ }
21
34
  class COS {
35
+ /** 生成 COS 上传签名表单 */
36
+ getFormData(Bucket, Key) {
37
+ const now = data.create_time || Math.round(Date.now() / 1000);
38
+ const exp = now + 1800; // 30分钟过期
39
+ const qKeyTime = now + ';' + exp;
40
+ const qSignAlgorithm = 'sha1';
41
+ const qAk = data.secret_id;
42
+ const policyData = {
43
+ expiration: new Date(exp * 1000).toISOString(),
44
+ conditions: [
45
+ { 'q-sign-algorithm': qSignAlgorithm },
46
+ { 'q-ak': qAk },
47
+ { 'q-sign-time': qKeyTime },
48
+ { bucket: Bucket },
49
+ { key: Key },
50
+ ],
51
+ };
52
+ const policy = JSON.stringify(policyData);
53
+ const signKey = HmacSHA1(qKeyTime, data.secret_key).toString(Hex);
54
+ const stringToSign = SHA1(policy).toString(Hex);
55
+ const qSignature = HmacSHA1(stringToSign, signKey).toString(Hex);
56
+ return {
57
+ key: Key,
58
+ policy: Base64.stringify(Utf8.parse(policy)),
59
+ success_action_status: 200,
60
+ 'x-cos-security-token': data.session_token,
61
+ 'q-sign-algorithm': qSignAlgorithm,
62
+ 'q-ak': qAk,
63
+ 'q-key-time': qKeyTime,
64
+ 'q-signature': qSignature,
65
+ };
66
+ }
67
+ /**
68
+ * 统一上传入口:内部按运行环境选择 plus.uploader 或 uni.uploadFile,
69
+ * 两条链路的 success / fail / getTask 回调形态一致。
70
+ */
71
+ uploadHandler(options) {
72
+ const { url, filePath, file, name = 'file', formData = {}, getTask, success, fail } = options;
73
+ // 链路 1:H5 宿主 webview + plus.uploader + 文件路径(canUsePlusUploader 内部判断,命中后不再走 uni.uploadFile)
74
+ // #ifdef WEB || H5
75
+ if (canUsePlusUploader(filePath)) {
76
+ let plusTask;
77
+ let settled = false;
78
+ const progressCallbacks = new Set();
79
+ const plusSuccess = (res) => {
80
+ if (settled) {
81
+ return;
82
+ }
83
+ settled = true;
84
+ success?.(res);
85
+ };
86
+ const plusFail = (err) => {
87
+ if (settled) {
88
+ return;
89
+ }
90
+ settled = true;
91
+ fail?.(err);
92
+ };
93
+ try {
94
+ plusTask = plus.uploader.createUpload(url, { method: 'POST' }, (upload, status) => {
95
+ const statusCode = Number(status) || 0;
96
+ if (statusCode > 0) {
97
+ plusSuccess({ statusCode, data: String(upload.responseText || '') });
98
+ }
99
+ else {
100
+ // 网络失败,未取得 HTTP 状态码
101
+ plusFail({ errMsg: 'uploadFile:fail' });
102
+ }
103
+ });
104
+ plusTask.addEventListener('statechanged', (upload) => {
105
+ const total = Number(upload.totalSize) || 0;
106
+ const sent = Number(upload.uploadedSize) || 0;
107
+ const progress = total > 0 ? Math.round((sent / total) * 100) : 0;
108
+ progressCallbacks.forEach((cb) => cb({ progress, totalBytesSent: sent, totalBytesExpectedToSend: total }));
109
+ });
110
+ Object.keys(formData).forEach((key) => {
111
+ const value = formData[key];
112
+ if (value !== undefined && value !== null) {
113
+ plusTask.addData(key, String(value));
114
+ }
115
+ });
116
+ if (plusTask.addFile(filePath, { key: name })) {
117
+ const taskLike = {
118
+ abort: () => {
119
+ if (settled) {
120
+ return;
121
+ }
122
+ settled = true;
123
+ plusTask.abort();
124
+ fail?.({ errMsg: 'uploadFile:fail abort' });
125
+ },
126
+ onProgressUpdate: (cb) => {
127
+ progressCallbacks.add(cb);
128
+ },
129
+ offProgressUpdate: (cb) => {
130
+ progressCallbacks.delete(cb);
131
+ },
132
+ };
133
+ getTask?.(taskLike);
134
+ plusTask.start();
135
+ return;
136
+ }
137
+ // 已确定走 plus 链路:路径无法被 HTML5+ 读取(如 blob/content)时直接失败,不再回退 uni.uploadFile
138
+ plusFail({ errMsg: 'uploadFile:fail' });
139
+ plusTask.abort();
140
+ }
141
+ catch (e) {
142
+ plusFail({ errMsg: 'uploadFile:fail' });
143
+ plusTask?.abort();
144
+ }
145
+ return;
146
+ }
147
+ // #endif
148
+ // 链路 2:uni.uploadFile(默认链路)
149
+ const task = uni.uploadFile({
150
+ url,
151
+ filePath,
152
+ file,
153
+ name,
154
+ formData,
155
+ success: (res) => {
156
+ success?.({ statusCode: res.statusCode, data: String(res.data ?? '') });
157
+ },
158
+ fail: (err) => {
159
+ fail?.(err);
160
+ },
161
+ });
162
+ getTask?.(task);
163
+ }
164
+ /** 处理上传结果:成功 / token 过期重试 / 失败 */
165
+ handleUploadResult(params, cosHost, statusCode, body, resolve, reject) {
166
+ if (statusCode === 200) {
167
+ resolve({ Location: `${cosHost}/${params.Key}` });
168
+ return;
169
+ }
170
+ const resBody = String(body || '');
171
+ const codeMatch = resBody.match(/<Code>([^<>]+)<\/Code>/);
172
+ if (codeMatch && codeMatch[1] === 'ExpiredToken' && data.expire_time !== 0) {
173
+ data.expire_time = 0;
174
+ checkRefreshToken()
175
+ .then(() => {
176
+ this.uploadFile(params)
177
+ .then((e) => {
178
+ resolve(e);
179
+ })
180
+ .catch((e) => {
181
+ reject(e);
182
+ });
183
+ })
184
+ .catch((e) => {
185
+ reject(e);
186
+ });
187
+ return;
188
+ }
189
+ const err = {
190
+ code: statusCode,
191
+ msg: 'Upload Fail',
192
+ };
193
+ const msgMatch = resBody.match(/<Message>([^<>]+)<\/Message>/);
194
+ if (msgMatch) {
195
+ err.msg = msgMatch[1];
196
+ }
197
+ reject(err);
198
+ }
22
199
  async uploadFile(params) {
23
200
  const { Bucket, Region, Key, FilePath, File } = params;
201
+ const cosHost = `${Bucket}.cos.${Region}.myqcloud.com`;
24
202
  return new Promise((resolve, reject) => {
25
- const cosHost = `${Bucket}.cos.${Region}.myqcloud.com`;
26
- const now = data.create_time || Math.round(Date.now() / 1000);
27
- const exp = now + 1800; // 30分钟过期
28
- const qKeyTime = now + ';' + exp;
29
- const qSignAlgorithm = 'sha1';
30
- const qAk = data.secret_id;
31
- const policyData = {
32
- expiration: new Date(exp * 1000).toISOString(),
33
- conditions: [
34
- { 'q-sign-algorithm': qSignAlgorithm },
35
- { 'q-ak': qAk },
36
- { 'q-sign-time': qKeyTime },
37
- { bucket: Bucket },
38
- { key: Key },
39
- ],
40
- };
41
- const policy = JSON.stringify(policyData);
42
- const signKey = HmacSHA1(qKeyTime, data.secret_key).toString(Hex);
43
- const stringToSign = SHA1(policy).toString(Hex);
44
- const qSignature = HmacSHA1(stringToSign, signKey).toString(Hex);
45
- const FormData = {
46
- key: Key,
47
- policy: Base64.stringify(Utf8.parse(policy)),
48
- success_action_status: 200,
49
- 'x-cos-security-token': data.session_token,
50
- 'q-sign-algorithm': qSignAlgorithm,
51
- 'q-ak': qAk,
52
- 'q-key-time': qKeyTime,
53
- 'q-signature': qSignature,
54
- };
55
- const task = uni.uploadFile({
203
+ this.uploadHandler({
56
204
  url: `https://${cosHost}`,
57
205
  filePath: FilePath,
58
206
  file: File,
59
207
  name: 'file',
60
- formData: FormData,
208
+ formData: this.getFormData(Bucket, Key),
209
+ getTask: params.getTask,
61
210
  success: (res) => {
62
- if (res.statusCode === 200) {
63
- resolve({ Location: `${cosHost}/${Key}` });
64
- }
65
- else {
66
- const { data: e, statusCode } = res;
67
- const codeMatch = e.match(/<Code>([^<>]+)<\/Code>/);
68
- if (codeMatch && codeMatch[1] === 'ExpiredToken' && data.expire_time !== 0) {
69
- data.expire_time = 0;
70
- checkRefreshToken()
71
- .then(() => {
72
- this.uploadFile(params)
73
- .then((e) => {
74
- resolve(e);
75
- })
76
- .catch((e) => {
77
- reject(e);
78
- });
79
- })
80
- .catch((e) => {
81
- reject(e);
82
- });
83
- }
84
- else {
85
- const err = {
86
- code: statusCode,
87
- msg: 'Upload Fail',
88
- };
89
- const msgMatch = e.match(/<Message>([^<>]+)<\/Message>/);
90
- if (msgMatch) {
91
- err.msg = msgMatch[1];
92
- }
93
- reject(err);
94
- }
95
- }
211
+ this.handleUploadResult(params, cosHost, res.statusCode, res.data, resolve, reject);
96
212
  },
97
213
  fail: (err) => {
98
214
  reject({
@@ -100,7 +216,6 @@ class COS {
100
216
  });
101
217
  },
102
218
  });
103
- params.getTask?.(task);
104
219
  });
105
220
  }
106
221
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@zfqh/uniapp-x",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "uniapp x 公共模块",
5
5
  "author": "yinjiazeng@163.com",
6
- "license": "MIT",
6
+ "license": "UNLICENSED",
7
7
  "main": "lib/index.js",
8
8
  "types": "lib/index.d.ts",
9
9
  "publishConfig": {
@@ -20,11 +20,10 @@
20
20
  "files": [
21
21
  "lib",
22
22
  "types",
23
- "CHANGELOG.md",
24
23
  "README.md"
25
24
  ],
26
25
  "dependencies": {
27
- "@zfqh/uview-x": "0.0.7",
26
+ "@zfqh/uview-x": "0.0.9",
28
27
  "crypto-es": "^3.1.3",
29
28
  "pinia": "2.1.7",
30
29
  "qs": "^6.13.0",
@@ -36,5 +35,5 @@
36
35
  "@types/qs": "^6.9.16",
37
36
  "@types/url-parse": "^1.4.11"
38
37
  },
39
- "gitHead": "59595760375b8c8c71d6abe4e860d4ba8ca915a1"
38
+ "gitHead": "5dbc4d414ca9c84f7b65abbf92f8ce0f3750a1f3"
40
39
  }
package/CHANGELOG.md DELETED
@@ -1,84 +0,0 @@
1
- # Change Log
2
-
3
- All notable changes to this project will be documented in this file.
4
- See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
-
6
- ## [0.0.7](http://10.10.10.16/caoben/front-end-tools/compare/@zfqh/uniapp-x@0.0.6...@zfqh/uniapp-x@0.0.7) (2026-11-11)
7
-
8
- **Note:** Version bump only for package @zfqh/uniapp-x
9
-
10
-
11
-
12
-
13
-
14
- ## [0.0.6](http://10.10.10.16/caoben/front-end-tools/compare/@zfqh/uniapp-x@0.0.5...@zfqh/uniapp-x@0.0.6) (2026-11-11)
15
-
16
- **Note:** Version bump only for package @zfqh/uniapp-x
17
-
18
-
19
-
20
-
21
-
22
- ## [0.0.5](http://10.10.10.16/caoben/front-end-tools/compare/@zfqh/uniapp-x@0.0.4...@zfqh/uniapp-x@0.0.5) (2026-11-11)
23
-
24
- **Note:** Version bump only for package @zfqh/uniapp-x
25
-
26
-
27
-
28
-
29
-
30
- ## [0.0.4](http://10.10.10.16/caoben/front-end-tools/compare/@zfqh/uniapp-x@0.0.3...@zfqh/uniapp-x@0.0.4) (2026-11-10)
31
-
32
- **Note:** Version bump only for package @zfqh/uniapp-x
33
-
34
-
35
-
36
-
37
-
38
- ## [0.0.3](http://10.10.10.16/caoben/front-end-tools/compare/@zfqh/uniapp-x@0.0.2...@zfqh/uniapp-x@0.0.3) (2026-11-10)
39
-
40
- **Note:** Version bump only for package @zfqh/uniapp-x
41
-
42
-
43
-
44
-
45
-
46
- ## [0.0.2](http://10.10.10.16/caoben/front-end-tools/compare/@zfqh/uniapp-x@0.0.1...@zfqh/uniapp-x@0.0.2) (2026-11-09)
47
-
48
- **Note:** Version bump only for package @zfqh/uniapp-x
49
-
50
-
51
-
52
-
53
-
54
- ## 0.0.1 (2026-11-09)
55
-
56
- **Note:** Version bump only for package @zfqh/uniapp-x
57
-
58
-
59
-
60
-
61
-
62
- # Change Log
63
-
64
- ## Unreleased
65
-
66
- - `cleanEnv` 新增 `combine` 选项(默认 `false`)。`combine: true` 时在构建期为 `src|lib/configs/env` 提供虚拟模块 `virtual:env`(不写文件),结构保持 glob 的 `{ 路径: { default: config } }`;生产裁剪仍然生效,无关环境在索引中为 `{ default: {} }` 且不 import;`.env.*` 变化时刷新虚拟模块;`defineConfig` 对用户已提供的 `cleanEnv` 不再重复追加。
67
- - env 文件扩展名支持 `.js`、`.ts`、`.mjs`、`.mts`、`.uts`,`cleanEnv` 生产裁剪、`getEnvHash`、`configs/env.ts` 解析同步支持 `.uts`。
68
- - **BREAKING**:env 模块移除 App 条件编译,恢复为 `const modules = import.meta.glob('@env/.env.*.*', { eager: true })`;App 工程需在 `@gmcb/vite` 中显式配置 `cleanEnv({ combine: true })`。
69
- - 修复 App 蒸汽模式下的 CJS 依赖互操作:`qs`、`url-parse` 在 App Vapor 打包时无法以 ESM 方式解析,`useWebview` / `useRedirect` 改为使用包内 `utils/query.ts`(`parseQuery` / `stringifyQuery`)与新增 `utils/url.ts`(`SimpleUrl`),H5/MP 行为保持一致。
70
- - `tsc` 构建后的 `lib` 需要配合 `pnpm copy:build` 复制 `src/styles/**` 等非 TS 资源(`var.module.scss`),否则 App 编译会报 `Could not resolve "../styles/var.module.scss"`。
71
- - Pinia store id 统一为模块名(`app`、`dict`、`im`、`launch`、`location`、`login`、`network`、`system`),移除 `uniapp-x-` 前缀,与文件名、`TYPES.STORAGE` key 的命名保持一致。
72
- - 移除 `docs/` 文档站点与 `.vitepress/`,删除 `docs:dev` / `docs:build` 脚本(暂时不维护);`package.json` 的 `files` 不再包含 `docs`。
73
-
74
- ## 0.0.0
75
-
76
- - 从 `@zfqh/uniapp` fork 的 uni-app x 分支。
77
- - **BREAKING**:移除 Vuex 兼容层(`uni.$store`、`this.$store`、`commit`、`dispatch`、`getters`、`rootState`、`useStore(name)` 等),状态管理统一为 Pinia setup store。插件安装时执行 `app.use(pinia)` 安装包内唯一实例;普通 TS 模块使用 `useXxxStore(pinia)`,不要创建第二个 Pinia。
78
- - **BREAKING**:移除全部 mixin(app、badge、login、redirect、share、show、webview、model、deep、debounce),替换为组合式 hooks:`useAppLifecycle`、`useBadge` / `useAppBadge`、`useNeedLogin`、`useRedirect`、`useShare`、`usePageShow`、`useWebview`。`model` 改用原生 `v-model`,`deep` 改用 `:deep()` / `styleIsolation`,`debounce` 改用 `Debounce` 工具类。
79
- - **BREAKING**:移除全部 `g-*` 公共组件,同步下线组件文档和 easycom 配置;业务侧改用 `@zfqh/uview-x` 的 `uv-*` 组件或业务自定义组件。
80
- - 新增 8 个 Pinia setup store:`useAppStore`、`useDictStore`、`useImStore`、`useLaunchStore`、`useLocationStore`、`useLoginStore`、`useNetworkStore`、`useSystemStore`;状态统一放在 `info`(`useLaunchStore` 额外有 `key`),提供 `setXxxInfo` / `getXxxInfo` 等 action 与 `isLogin`、`isConnected` computed。
81
- - 新增 hooks:`usePageShow`、`useBadge`、`useAppBadge`、`useAppLifecycle`、`useShare`、`useWebview`、`useRedirect`,与既有 `useNeedLogin`、`useOverlay`、`usePopup`、`useVersion`、`useI18n`、`useReferrer` 一并从根入口导出。
82
- - COS 签名依赖由 `crypto-js` 替换为 `crypto-es`(ESM)。
83
- - 环境文件默认在 H5 / 小程序通过 `import.meta.glob` 加载;App 端需配合 `@gmcb/vite` 的 `cleanEnv({ combine: true })` 使用虚拟 env 索引。
84
- - 文档重构:导航与侧边栏移除组件、混入;样式文档迁移到「指南 / 样式」;新增「从 Vuex/mixin 迁移」页,API 侧边栏改为状态管理与 Hooks。
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 aniu
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.