@howuse/feedback 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/README.md +237 -0
- package/dist/index.cjs +146 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.mjs +146 -0
- package/dist/native/index.d.ts +14 -0
- package/dist/types.d.ts +105 -0
- package/native/howuse_feedback.dll +0 -0
- package/native/howuse_feedback.h +90 -0
- package/native/libhowuse_feedback.dylib +0 -0
- package/native/libhowuse_feedback.h +90 -0
- package/native/libhowuse_feedback_arm64.dylib +0 -0
- package/native/libhowuse_feedback_arm64.h +90 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# @howuse/feedback
|
|
2
|
+
|
|
3
|
+
Node.js/Electron SDK for user feedback and activation management.
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @howuse/feedback
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
或者使用本地路径安装:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install ./feedback-sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 快速开始
|
|
18
|
+
|
|
19
|
+
### Node.js 示例
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { initFeedbackClient } from '@howuse/feedback';
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
// 初始化客户端
|
|
26
|
+
const client = await initFeedbackClient({
|
|
27
|
+
baseUrl: 'http://localhost:8080',
|
|
28
|
+
softwareId: 1,
|
|
29
|
+
version: '1.0.0',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// 检查是否可用
|
|
33
|
+
if (!client.enabled) {
|
|
34
|
+
console.warn('SDK is disabled, some features may not work');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 提交反馈
|
|
39
|
+
const feedbackResult = await client.feedback({
|
|
40
|
+
content: 'This is a test feedback',
|
|
41
|
+
email: 'user@example.com', // 可选
|
|
42
|
+
});
|
|
43
|
+
console.log('Feedback result:', feedbackResult);
|
|
44
|
+
|
|
45
|
+
// 激活软件
|
|
46
|
+
const activateResult = await client.activate({
|
|
47
|
+
machineCode: 'your-machine-code',
|
|
48
|
+
code: 'your-activation-code',
|
|
49
|
+
});
|
|
50
|
+
console.log('Activation result:', activateResult);
|
|
51
|
+
|
|
52
|
+
// 检查激活状态
|
|
53
|
+
const checkResult = await client.check({
|
|
54
|
+
machineCode: 'your-machine-code',
|
|
55
|
+
});
|
|
56
|
+
if (checkResult.expired) {
|
|
57
|
+
console.log('License expired:', checkResult.reason);
|
|
58
|
+
} else {
|
|
59
|
+
console.log('License valid until:', checkResult.expireAt);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
main().catch(console.error);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Electron 主进程示例
|
|
67
|
+
|
|
68
|
+
在 Electron 的 `main.js` 或 `main.ts` 中:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { app, BrowserWindow } from 'electron';
|
|
72
|
+
import { initFeedbackClient } from '@howuse/feedback';
|
|
73
|
+
|
|
74
|
+
let feedbackClient: any;
|
|
75
|
+
|
|
76
|
+
async function initApp() {
|
|
77
|
+
// 初始化 SDK
|
|
78
|
+
feedbackClient = await initFeedbackClient({
|
|
79
|
+
baseUrl: 'http://localhost:8080',
|
|
80
|
+
softwareId: 1,
|
|
81
|
+
version: app.getVersion(),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 根据 enabled 状态决定是否显示反馈入口
|
|
85
|
+
if (feedbackClient.enabled) {
|
|
86
|
+
// 可以设置全局菜单或窗口中的反馈按钮
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
app.whenReady().then(() => {
|
|
91
|
+
initApp();
|
|
92
|
+
createWindow();
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## API 参考
|
|
97
|
+
|
|
98
|
+
### `initFeedbackClient(options: InitOptions): Promise<FeedbackClient>`
|
|
99
|
+
|
|
100
|
+
初始化 Feedback 客户端。
|
|
101
|
+
|
|
102
|
+
**参数:**
|
|
103
|
+
|
|
104
|
+
- `options.baseUrl` (string): 服务器基础 URL
|
|
105
|
+
- `options.softwareId` (number): 软件 ID
|
|
106
|
+
- `options.version` (string): 软件版本号
|
|
107
|
+
- `options.timeoutMs` (number, 可选): 请求超时时间(毫秒),默认 30000
|
|
108
|
+
|
|
109
|
+
**返回值:**
|
|
110
|
+
|
|
111
|
+
返回一个 `FeedbackClient` 实例。即使初始化失败,也不会抛出异常,而是返回 `enabled=false` 的客户端。
|
|
112
|
+
|
|
113
|
+
### `FeedbackClient`
|
|
114
|
+
|
|
115
|
+
#### 属性
|
|
116
|
+
|
|
117
|
+
- `enabled: boolean` - 指示 SDK 是否可用
|
|
118
|
+
|
|
119
|
+
#### 方法
|
|
120
|
+
|
|
121
|
+
##### `feedback(data: FeedbackPayload): Promise<FeedbackResult>`
|
|
122
|
+
|
|
123
|
+
提交用户反馈。
|
|
124
|
+
|
|
125
|
+
**参数:**
|
|
126
|
+
|
|
127
|
+
- `data.content` (string): 反馈内容(必填)
|
|
128
|
+
- `data.email` (string, 可选): 邮箱地址
|
|
129
|
+
|
|
130
|
+
**返回值:**
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
{
|
|
134
|
+
success: boolean;
|
|
135
|
+
id?: number;
|
|
136
|
+
error?: string;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
##### `activate(data: ActivationRequest): Promise<ActivationResult>`
|
|
141
|
+
|
|
142
|
+
激活软件。
|
|
143
|
+
|
|
144
|
+
**参数:**
|
|
145
|
+
|
|
146
|
+
- `data.machineCode` (string): 机器码
|
|
147
|
+
- `data.code` (string): 激活码
|
|
148
|
+
|
|
149
|
+
**返回值:**
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
{
|
|
153
|
+
success: boolean;
|
|
154
|
+
expireAt?: string;
|
|
155
|
+
error?: string;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
##### `check(data: ActivationCheckRequest): Promise<ActivationCheckResponse>`
|
|
160
|
+
|
|
161
|
+
检查激活状态。
|
|
162
|
+
|
|
163
|
+
**重要:** 如果调用失败(网络错误或服务器返回非 2xx),SDK 会直接返回 `expired: true`,而不是抛出异常。这确保了在无法联系服务端时,默认按"授权已到期"处理。
|
|
164
|
+
|
|
165
|
+
**参数:**
|
|
166
|
+
|
|
167
|
+
- `data.machineCode` (string): 机器码
|
|
168
|
+
|
|
169
|
+
**返回值:**
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
{
|
|
173
|
+
expired: boolean;
|
|
174
|
+
activated?: boolean;
|
|
175
|
+
expireAt?: string;
|
|
176
|
+
reason?: string;
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 错误处理
|
|
181
|
+
|
|
182
|
+
SDK 采用"安全降级"策略:
|
|
183
|
+
|
|
184
|
+
1. **初始化失败**:不会抛出异常,而是返回 `enabled=false` 的客户端
|
|
185
|
+
2. **方法调用**:当 `enabled=false` 时,所有方法都会返回失败结果,而不是抛出异常
|
|
186
|
+
3. **激活检查失败**:`check` 方法在失败时会返回 `expired: true`,确保在无法联系服务端时默认按"授权已到期"处理
|
|
187
|
+
|
|
188
|
+
## 动态库支持
|
|
189
|
+
|
|
190
|
+
SDK 支持通过 Go 语言实现的动态库(native library)提供所有 HTTP 请求功能。如果动态库可用,SDK 会自动使用;否则会回退到纯 HTTP 实现。
|
|
191
|
+
|
|
192
|
+
### 动态库构建
|
|
193
|
+
|
|
194
|
+
动态库使用 Go 语言实现,源码位于项目根目录的 `native-sdk/` 目录。构建方法:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# macOS/Linux
|
|
198
|
+
cd native-sdk
|
|
199
|
+
./build.sh
|
|
200
|
+
|
|
201
|
+
# Windows
|
|
202
|
+
cd native-sdk
|
|
203
|
+
build.bat
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
构建完成后,动态库文件会输出到 `feedback-sdk/native/` 目录:
|
|
207
|
+
|
|
208
|
+
- macOS: `native/libhowuse_feedback.dylib`
|
|
209
|
+
- Windows: `native/howuse_feedback.dll`
|
|
210
|
+
- Linux: `native/libhowuse_feedback.so`
|
|
211
|
+
|
|
212
|
+
### 动态库优势
|
|
213
|
+
|
|
214
|
+
- **性能优化**:所有 HTTP 请求在 Go 层执行,性能更好
|
|
215
|
+
- **代码保护**:业务逻辑在编译后的动态库中,不易被逆向
|
|
216
|
+
- **统一实现**:HTTP 客户端逻辑统一在 Go 层管理
|
|
217
|
+
|
|
218
|
+
详细构建说明请参考 [native-sdk/README.md](./native-sdk/README.md)。
|
|
219
|
+
|
|
220
|
+
## 类型定义
|
|
221
|
+
|
|
222
|
+
所有类型都可以从包中导入:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import type {
|
|
226
|
+
InitOptions,
|
|
227
|
+
FeedbackPayload,
|
|
228
|
+
ActivationRequest,
|
|
229
|
+
ActivationCheckRequest,
|
|
230
|
+
// ... 其他类型
|
|
231
|
+
} from '@howuse/feedback';
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## 许可证
|
|
235
|
+
|
|
236
|
+
MIT
|
|
237
|
+
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const koffi = require("koffi");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
function tryLoadNativeLibrary() {
|
|
7
|
+
const platform = process.platform;
|
|
8
|
+
const arch = process.arch;
|
|
9
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
10
|
+
let libName;
|
|
11
|
+
if (platform === "darwin") {
|
|
12
|
+
if (arch === "arm64") {
|
|
13
|
+
libName = "libhowuse_feedback_arm64.dylib";
|
|
14
|
+
} else {
|
|
15
|
+
libName = "libhowuse_feedback.dylib";
|
|
16
|
+
}
|
|
17
|
+
} else if (platform === "win32") {
|
|
18
|
+
libName = "howuse_feedback.dll";
|
|
19
|
+
} else if (platform === "linux") {
|
|
20
|
+
libName = "libhowuse_feedback.so";
|
|
21
|
+
} else {
|
|
22
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
23
|
+
}
|
|
24
|
+
const libPath = path.join(packageRoot, "native", libName);
|
|
25
|
+
if (!fs.existsSync(libPath)) {
|
|
26
|
+
throw new Error(`Native library not found: ${libPath}. Please run 'npm run build:native' first.`);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return koffi.load(libPath);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new Error(`Failed to load native library from ${libPath}: ${error.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function createNativeClient() {
|
|
35
|
+
const lib = tryLoadNativeLibrary();
|
|
36
|
+
const InitClient = lib.func("InitClient", "str", ["str"]);
|
|
37
|
+
const SubmitFeedback = lib.func("SubmitFeedback", "str", ["str"]);
|
|
38
|
+
const Activate = lib.func("Activate", "str", ["str"]);
|
|
39
|
+
const CheckActivation = lib.func("CheckActivation", "str", ["str"]);
|
|
40
|
+
return {
|
|
41
|
+
async initClient(options) {
|
|
42
|
+
const json = JSON.stringify(options);
|
|
43
|
+
const result = InitClient(json);
|
|
44
|
+
return JSON.parse(result);
|
|
45
|
+
},
|
|
46
|
+
async submitFeedback(payload, softwareId) {
|
|
47
|
+
const json = JSON.stringify({ ...payload, softwareId });
|
|
48
|
+
const result = SubmitFeedback(json);
|
|
49
|
+
return JSON.parse(result);
|
|
50
|
+
},
|
|
51
|
+
async activate(request, softwareId) {
|
|
52
|
+
const json = JSON.stringify({ ...request, softwareId });
|
|
53
|
+
const result = Activate(json);
|
|
54
|
+
return JSON.parse(result);
|
|
55
|
+
},
|
|
56
|
+
async checkActivation(request, softwareId) {
|
|
57
|
+
const json = JSON.stringify({ ...request, softwareId });
|
|
58
|
+
const result = CheckActivation(json);
|
|
59
|
+
return JSON.parse(result);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
class FeedbackClient {
|
|
64
|
+
constructor(enabled, baseUrl, softwareId, version, nativeClient) {
|
|
65
|
+
this.enabled = enabled;
|
|
66
|
+
this.baseUrl = baseUrl;
|
|
67
|
+
this.softwareId = softwareId;
|
|
68
|
+
this.version = version;
|
|
69
|
+
this.nativeClient = nativeClient;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 提交反馈
|
|
73
|
+
*/
|
|
74
|
+
async feedback(data) {
|
|
75
|
+
if (!this.enabled) {
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
error: "sdk_disabled"
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return this.nativeClient.submitFeedback(data, this.softwareId);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 激活软件
|
|
85
|
+
*/
|
|
86
|
+
async activate(data) {
|
|
87
|
+
if (!this.enabled) {
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
error: "sdk_disabled"
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return this.nativeClient.activate(data, this.softwareId);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 检查激活状态
|
|
97
|
+
*/
|
|
98
|
+
async check(data) {
|
|
99
|
+
if (!this.enabled) {
|
|
100
|
+
return {
|
|
101
|
+
expired: true,
|
|
102
|
+
reason: "sdk_disabled_or_init_failed"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const result = await this.nativeClient.checkActivation(data, this.softwareId);
|
|
107
|
+
if (!result.expired && !result.activated) {
|
|
108
|
+
return {
|
|
109
|
+
expired: true,
|
|
110
|
+
reason: "Not activated"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return {
|
|
116
|
+
expired: true,
|
|
117
|
+
reason: error.message || "Activation check failed"
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function initFeedbackClient(options) {
|
|
123
|
+
if (!options.baseUrl || !options.softwareId || !options.version) {
|
|
124
|
+
throw new Error("baseUrl, softwareId, and version are required");
|
|
125
|
+
}
|
|
126
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
127
|
+
const nativeClient = createNativeClient();
|
|
128
|
+
let initResult;
|
|
129
|
+
try {
|
|
130
|
+
initResult = await nativeClient.initClient({
|
|
131
|
+
...options,
|
|
132
|
+
baseUrl
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return new FeedbackClient(false, baseUrl, options.softwareId, options.version, nativeClient);
|
|
136
|
+
}
|
|
137
|
+
return new FeedbackClient(
|
|
138
|
+
initResult.enabled,
|
|
139
|
+
baseUrl,
|
|
140
|
+
options.softwareId,
|
|
141
|
+
options.version,
|
|
142
|
+
nativeClient
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
exports.FeedbackClient = FeedbackClient;
|
|
146
|
+
exports.initFeedbackClient = initFeedbackClient;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type NativeClient } from './native';
|
|
2
|
+
import type { InitOptions, FeedbackPayload, FeedbackResult, ActivationRequest, ActivationResult, ActivationCheckRequest, ActivationCheckResponse } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* FeedbackClient 类
|
|
5
|
+
*/
|
|
6
|
+
export declare class FeedbackClient {
|
|
7
|
+
readonly enabled: boolean;
|
|
8
|
+
private readonly baseUrl;
|
|
9
|
+
private readonly softwareId;
|
|
10
|
+
private readonly version;
|
|
11
|
+
private readonly nativeClient;
|
|
12
|
+
constructor(enabled: boolean, baseUrl: string, softwareId: number, version: string, nativeClient: NativeClient);
|
|
13
|
+
/**
|
|
14
|
+
* 提交反馈
|
|
15
|
+
*/
|
|
16
|
+
feedback(data: FeedbackPayload): Promise<FeedbackResult>;
|
|
17
|
+
/**
|
|
18
|
+
* 激活软件
|
|
19
|
+
*/
|
|
20
|
+
activate(data: ActivationRequest): Promise<ActivationResult>;
|
|
21
|
+
/**
|
|
22
|
+
* 检查激活状态
|
|
23
|
+
*/
|
|
24
|
+
check(data: ActivationCheckRequest): Promise<ActivationCheckResponse>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 初始化 Feedback 客户端
|
|
28
|
+
* @param options 初始化选项
|
|
29
|
+
* @returns Promise<FeedbackClient>
|
|
30
|
+
*/
|
|
31
|
+
export declare function initFeedbackClient(options: InitOptions): Promise<FeedbackClient>;
|
|
32
|
+
export type { InitOptions, InitResult, FeedbackPayload, FeedbackResult, ActivationRequest, ActivationResult, ActivationCheckRequest, ActivationCheckResponse, } from './types';
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const koffi = require("koffi");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
function tryLoadNativeLibrary() {
|
|
5
|
+
const platform = process.platform;
|
|
6
|
+
const arch = process.arch;
|
|
7
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
8
|
+
let libName;
|
|
9
|
+
if (platform === "darwin") {
|
|
10
|
+
if (arch === "arm64") {
|
|
11
|
+
libName = "libhowuse_feedback_arm64.dylib";
|
|
12
|
+
} else {
|
|
13
|
+
libName = "libhowuse_feedback.dylib";
|
|
14
|
+
}
|
|
15
|
+
} else if (platform === "win32") {
|
|
16
|
+
libName = "howuse_feedback.dll";
|
|
17
|
+
} else if (platform === "linux") {
|
|
18
|
+
libName = "libhowuse_feedback.so";
|
|
19
|
+
} else {
|
|
20
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
21
|
+
}
|
|
22
|
+
const libPath = path.join(packageRoot, "native", libName);
|
|
23
|
+
if (!fs.existsSync(libPath)) {
|
|
24
|
+
throw new Error(`Native library not found: ${libPath}. Please run 'npm run build:native' first.`);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return koffi.load(libPath);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(`Failed to load native library from ${libPath}: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function createNativeClient() {
|
|
33
|
+
const lib = tryLoadNativeLibrary();
|
|
34
|
+
const InitClient = lib.func("InitClient", "str", ["str"]);
|
|
35
|
+
const SubmitFeedback = lib.func("SubmitFeedback", "str", ["str"]);
|
|
36
|
+
const Activate = lib.func("Activate", "str", ["str"]);
|
|
37
|
+
const CheckActivation = lib.func("CheckActivation", "str", ["str"]);
|
|
38
|
+
return {
|
|
39
|
+
async initClient(options) {
|
|
40
|
+
const json = JSON.stringify(options);
|
|
41
|
+
const result = InitClient(json);
|
|
42
|
+
return JSON.parse(result);
|
|
43
|
+
},
|
|
44
|
+
async submitFeedback(payload, softwareId) {
|
|
45
|
+
const json = JSON.stringify({ ...payload, softwareId });
|
|
46
|
+
const result = SubmitFeedback(json);
|
|
47
|
+
return JSON.parse(result);
|
|
48
|
+
},
|
|
49
|
+
async activate(request, softwareId) {
|
|
50
|
+
const json = JSON.stringify({ ...request, softwareId });
|
|
51
|
+
const result = Activate(json);
|
|
52
|
+
return JSON.parse(result);
|
|
53
|
+
},
|
|
54
|
+
async checkActivation(request, softwareId) {
|
|
55
|
+
const json = JSON.stringify({ ...request, softwareId });
|
|
56
|
+
const result = CheckActivation(json);
|
|
57
|
+
return JSON.parse(result);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
class FeedbackClient {
|
|
62
|
+
constructor(enabled, baseUrl, softwareId, version, nativeClient) {
|
|
63
|
+
this.enabled = enabled;
|
|
64
|
+
this.baseUrl = baseUrl;
|
|
65
|
+
this.softwareId = softwareId;
|
|
66
|
+
this.version = version;
|
|
67
|
+
this.nativeClient = nativeClient;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 提交反馈
|
|
71
|
+
*/
|
|
72
|
+
async feedback(data) {
|
|
73
|
+
if (!this.enabled) {
|
|
74
|
+
return {
|
|
75
|
+
success: false,
|
|
76
|
+
error: "sdk_disabled"
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return this.nativeClient.submitFeedback(data, this.softwareId);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 激活软件
|
|
83
|
+
*/
|
|
84
|
+
async activate(data) {
|
|
85
|
+
if (!this.enabled) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: "sdk_disabled"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return this.nativeClient.activate(data, this.softwareId);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 检查激活状态
|
|
95
|
+
*/
|
|
96
|
+
async check(data) {
|
|
97
|
+
if (!this.enabled) {
|
|
98
|
+
return {
|
|
99
|
+
expired: true,
|
|
100
|
+
reason: "sdk_disabled_or_init_failed"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const result = await this.nativeClient.checkActivation(data, this.softwareId);
|
|
105
|
+
if (!result.expired && !result.activated) {
|
|
106
|
+
return {
|
|
107
|
+
expired: true,
|
|
108
|
+
reason: "Not activated"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
expired: true,
|
|
115
|
+
reason: error.message || "Activation check failed"
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function initFeedbackClient(options) {
|
|
121
|
+
if (!options.baseUrl || !options.softwareId || !options.version) {
|
|
122
|
+
throw new Error("baseUrl, softwareId, and version are required");
|
|
123
|
+
}
|
|
124
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
125
|
+
const nativeClient = createNativeClient();
|
|
126
|
+
let initResult;
|
|
127
|
+
try {
|
|
128
|
+
initResult = await nativeClient.initClient({
|
|
129
|
+
...options,
|
|
130
|
+
baseUrl
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return new FeedbackClient(false, baseUrl, options.softwareId, options.version, nativeClient);
|
|
134
|
+
}
|
|
135
|
+
return new FeedbackClient(
|
|
136
|
+
initResult.enabled,
|
|
137
|
+
baseUrl,
|
|
138
|
+
options.softwareId,
|
|
139
|
+
options.version,
|
|
140
|
+
nativeClient
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
export {
|
|
144
|
+
FeedbackClient,
|
|
145
|
+
initFeedbackClient
|
|
146
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { InitOptions, InitResult, FeedbackPayload, FeedbackResult, ActivationRequest, ActivationResult, ActivationCheckRequest, ActivationCheckResponse } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Native 客户端接口
|
|
4
|
+
*/
|
|
5
|
+
export interface NativeClient {
|
|
6
|
+
initClient(options: InitOptions): Promise<InitResult>;
|
|
7
|
+
submitFeedback(payload: FeedbackPayload, softwareId: number): Promise<FeedbackResult>;
|
|
8
|
+
activate(request: ActivationRequest, softwareId: number): Promise<ActivationResult>;
|
|
9
|
+
checkActivation(request: ActivationCheckRequest, softwareId: number): Promise<ActivationCheckResponse>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 创建 Native 客户端
|
|
13
|
+
*/
|
|
14
|
+
export declare function createNativeClient(): NativeClient;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 初始化选项
|
|
3
|
+
*/
|
|
4
|
+
export interface InitOptions {
|
|
5
|
+
/** 服务器基础 URL */
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
/** 软件 ID */
|
|
8
|
+
softwareId: number;
|
|
9
|
+
/** 软件版本号 */
|
|
10
|
+
version: string;
|
|
11
|
+
/** 请求超时时间(毫秒),可选 */
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 初始化结果
|
|
16
|
+
*/
|
|
17
|
+
export interface InitResult {
|
|
18
|
+
/** 是否启用 */
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
/** 错误信息(如果初始化失败) */
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 版本信息请求
|
|
25
|
+
*/
|
|
26
|
+
export interface VersionRequest {
|
|
27
|
+
softwareId: number;
|
|
28
|
+
version: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 版本信息响应
|
|
32
|
+
*/
|
|
33
|
+
export interface VersionResponse {
|
|
34
|
+
ok: boolean;
|
|
35
|
+
id?: number;
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 活跃度上报请求
|
|
40
|
+
*/
|
|
41
|
+
export interface ActivityRequest {
|
|
42
|
+
softwareId: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 活跃度上报响应
|
|
46
|
+
*/
|
|
47
|
+
export interface ActivityResponse {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
id?: number;
|
|
50
|
+
error?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 反馈提交请求
|
|
54
|
+
*/
|
|
55
|
+
export interface FeedbackPayload {
|
|
56
|
+
/** 反馈内容 */
|
|
57
|
+
content: string;
|
|
58
|
+
/** 邮箱(可选) */
|
|
59
|
+
email?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 反馈提交响应
|
|
63
|
+
*/
|
|
64
|
+
export interface FeedbackResult {
|
|
65
|
+
success: boolean;
|
|
66
|
+
id?: number;
|
|
67
|
+
error?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 激活请求
|
|
71
|
+
*/
|
|
72
|
+
export interface ActivationRequest {
|
|
73
|
+
/** 机器码 */
|
|
74
|
+
machineCode: string;
|
|
75
|
+
/** 激活码 */
|
|
76
|
+
code: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 激活响应
|
|
80
|
+
*/
|
|
81
|
+
export interface ActivationResult {
|
|
82
|
+
success: boolean;
|
|
83
|
+
expireAt?: string;
|
|
84
|
+
error?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 激活检查请求
|
|
88
|
+
*/
|
|
89
|
+
export interface ActivationCheckRequest {
|
|
90
|
+
/** 机器码 */
|
|
91
|
+
machineCode: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 激活检查响应
|
|
95
|
+
*/
|
|
96
|
+
export interface ActivationCheckResponse {
|
|
97
|
+
/** 是否过期 */
|
|
98
|
+
expired: boolean;
|
|
99
|
+
/** 是否已激活 */
|
|
100
|
+
activated?: boolean;
|
|
101
|
+
/** 过期时间 */
|
|
102
|
+
expireAt?: string;
|
|
103
|
+
/** 错误原因 */
|
|
104
|
+
reason?: string;
|
|
105
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
|
2
|
+
|
|
3
|
+
/* package command-line-arguments */
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
#line 1 "cgo-builtin-export-prolog"
|
|
7
|
+
|
|
8
|
+
#include <stddef.h>
|
|
9
|
+
|
|
10
|
+
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
|
11
|
+
#define GO_CGO_EXPORT_PROLOGUE_H
|
|
12
|
+
|
|
13
|
+
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
|
14
|
+
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
|
15
|
+
#endif
|
|
16
|
+
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
/* Start of preamble from import "C" comments. */
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
#line 3 "main.go"
|
|
23
|
+
|
|
24
|
+
#include <stdlib.h>
|
|
25
|
+
|
|
26
|
+
#line 1 "cgo-generated-wrapper"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/* End of preamble from import "C" comments. */
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/* Start of boilerplate cgo prologue. */
|
|
33
|
+
#line 1 "cgo-gcc-export-header-prolog"
|
|
34
|
+
|
|
35
|
+
#ifndef GO_CGO_PROLOGUE_H
|
|
36
|
+
#define GO_CGO_PROLOGUE_H
|
|
37
|
+
|
|
38
|
+
typedef signed char GoInt8;
|
|
39
|
+
typedef unsigned char GoUint8;
|
|
40
|
+
typedef short GoInt16;
|
|
41
|
+
typedef unsigned short GoUint16;
|
|
42
|
+
typedef int GoInt32;
|
|
43
|
+
typedef unsigned int GoUint32;
|
|
44
|
+
typedef long long GoInt64;
|
|
45
|
+
typedef unsigned long long GoUint64;
|
|
46
|
+
typedef GoInt64 GoInt;
|
|
47
|
+
typedef GoUint64 GoUint;
|
|
48
|
+
typedef size_t GoUintptr;
|
|
49
|
+
typedef float GoFloat32;
|
|
50
|
+
typedef double GoFloat64;
|
|
51
|
+
#ifdef _MSC_VER
|
|
52
|
+
#include <complex.h>
|
|
53
|
+
typedef _Fcomplex GoComplex64;
|
|
54
|
+
typedef _Dcomplex GoComplex128;
|
|
55
|
+
#else
|
|
56
|
+
typedef float _Complex GoComplex64;
|
|
57
|
+
typedef double _Complex GoComplex128;
|
|
58
|
+
#endif
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
static assertion to make sure the file is being used on architecture
|
|
62
|
+
at least with matching size of GoInt.
|
|
63
|
+
*/
|
|
64
|
+
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
|
65
|
+
|
|
66
|
+
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
|
67
|
+
typedef _GoString_ GoString;
|
|
68
|
+
#endif
|
|
69
|
+
typedef void *GoMap;
|
|
70
|
+
typedef void *GoChan;
|
|
71
|
+
typedef struct { void *t; void *v; } GoInterface;
|
|
72
|
+
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
|
73
|
+
|
|
74
|
+
#endif
|
|
75
|
+
|
|
76
|
+
/* End of boilerplate cgo prologue. */
|
|
77
|
+
|
|
78
|
+
#ifdef __cplusplus
|
|
79
|
+
extern "C" {
|
|
80
|
+
#endif
|
|
81
|
+
|
|
82
|
+
extern __declspec(dllexport) char* InitClient(char* jsonOptions);
|
|
83
|
+
extern __declspec(dllexport) char* SubmitFeedback(char* jsonPayload);
|
|
84
|
+
extern __declspec(dllexport) char* Activate(char* jsonRequest);
|
|
85
|
+
extern __declspec(dllexport) char* CheckActivation(char* jsonRequest);
|
|
86
|
+
extern __declspec(dllexport) void FreeString(char* ptr);
|
|
87
|
+
|
|
88
|
+
#ifdef __cplusplus
|
|
89
|
+
}
|
|
90
|
+
#endif
|
|
Binary file
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
|
2
|
+
|
|
3
|
+
/* package command-line-arguments */
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
#line 1 "cgo-builtin-export-prolog"
|
|
7
|
+
|
|
8
|
+
#include <stddef.h>
|
|
9
|
+
|
|
10
|
+
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
|
11
|
+
#define GO_CGO_EXPORT_PROLOGUE_H
|
|
12
|
+
|
|
13
|
+
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
|
14
|
+
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
|
15
|
+
#endif
|
|
16
|
+
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
/* Start of preamble from import "C" comments. */
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
#line 3 "main.go"
|
|
23
|
+
|
|
24
|
+
#include <stdlib.h>
|
|
25
|
+
|
|
26
|
+
#line 1 "cgo-generated-wrapper"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/* End of preamble from import "C" comments. */
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/* Start of boilerplate cgo prologue. */
|
|
33
|
+
#line 1 "cgo-gcc-export-header-prolog"
|
|
34
|
+
|
|
35
|
+
#ifndef GO_CGO_PROLOGUE_H
|
|
36
|
+
#define GO_CGO_PROLOGUE_H
|
|
37
|
+
|
|
38
|
+
typedef signed char GoInt8;
|
|
39
|
+
typedef unsigned char GoUint8;
|
|
40
|
+
typedef short GoInt16;
|
|
41
|
+
typedef unsigned short GoUint16;
|
|
42
|
+
typedef int GoInt32;
|
|
43
|
+
typedef unsigned int GoUint32;
|
|
44
|
+
typedef long long GoInt64;
|
|
45
|
+
typedef unsigned long long GoUint64;
|
|
46
|
+
typedef GoInt64 GoInt;
|
|
47
|
+
typedef GoUint64 GoUint;
|
|
48
|
+
typedef size_t GoUintptr;
|
|
49
|
+
typedef float GoFloat32;
|
|
50
|
+
typedef double GoFloat64;
|
|
51
|
+
#ifdef _MSC_VER
|
|
52
|
+
#include <complex.h>
|
|
53
|
+
typedef _Fcomplex GoComplex64;
|
|
54
|
+
typedef _Dcomplex GoComplex128;
|
|
55
|
+
#else
|
|
56
|
+
typedef float _Complex GoComplex64;
|
|
57
|
+
typedef double _Complex GoComplex128;
|
|
58
|
+
#endif
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
static assertion to make sure the file is being used on architecture
|
|
62
|
+
at least with matching size of GoInt.
|
|
63
|
+
*/
|
|
64
|
+
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
|
65
|
+
|
|
66
|
+
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
|
67
|
+
typedef _GoString_ GoString;
|
|
68
|
+
#endif
|
|
69
|
+
typedef void *GoMap;
|
|
70
|
+
typedef void *GoChan;
|
|
71
|
+
typedef struct { void *t; void *v; } GoInterface;
|
|
72
|
+
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
|
73
|
+
|
|
74
|
+
#endif
|
|
75
|
+
|
|
76
|
+
/* End of boilerplate cgo prologue. */
|
|
77
|
+
|
|
78
|
+
#ifdef __cplusplus
|
|
79
|
+
extern "C" {
|
|
80
|
+
#endif
|
|
81
|
+
|
|
82
|
+
extern char* InitClient(char* jsonOptions);
|
|
83
|
+
extern char* SubmitFeedback(char* jsonPayload);
|
|
84
|
+
extern char* Activate(char* jsonRequest);
|
|
85
|
+
extern char* CheckActivation(char* jsonRequest);
|
|
86
|
+
extern void FreeString(char* ptr);
|
|
87
|
+
|
|
88
|
+
#ifdef __cplusplus
|
|
89
|
+
}
|
|
90
|
+
#endif
|
|
Binary file
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
|
2
|
+
|
|
3
|
+
/* package command-line-arguments */
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
#line 1 "cgo-builtin-export-prolog"
|
|
7
|
+
|
|
8
|
+
#include <stddef.h>
|
|
9
|
+
|
|
10
|
+
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
|
11
|
+
#define GO_CGO_EXPORT_PROLOGUE_H
|
|
12
|
+
|
|
13
|
+
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
|
14
|
+
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
|
15
|
+
#endif
|
|
16
|
+
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
/* Start of preamble from import "C" comments. */
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
#line 3 "main.go"
|
|
23
|
+
|
|
24
|
+
#include <stdlib.h>
|
|
25
|
+
|
|
26
|
+
#line 1 "cgo-generated-wrapper"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/* End of preamble from import "C" comments. */
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/* Start of boilerplate cgo prologue. */
|
|
33
|
+
#line 1 "cgo-gcc-export-header-prolog"
|
|
34
|
+
|
|
35
|
+
#ifndef GO_CGO_PROLOGUE_H
|
|
36
|
+
#define GO_CGO_PROLOGUE_H
|
|
37
|
+
|
|
38
|
+
typedef signed char GoInt8;
|
|
39
|
+
typedef unsigned char GoUint8;
|
|
40
|
+
typedef short GoInt16;
|
|
41
|
+
typedef unsigned short GoUint16;
|
|
42
|
+
typedef int GoInt32;
|
|
43
|
+
typedef unsigned int GoUint32;
|
|
44
|
+
typedef long long GoInt64;
|
|
45
|
+
typedef unsigned long long GoUint64;
|
|
46
|
+
typedef GoInt64 GoInt;
|
|
47
|
+
typedef GoUint64 GoUint;
|
|
48
|
+
typedef size_t GoUintptr;
|
|
49
|
+
typedef float GoFloat32;
|
|
50
|
+
typedef double GoFloat64;
|
|
51
|
+
#ifdef _MSC_VER
|
|
52
|
+
#include <complex.h>
|
|
53
|
+
typedef _Fcomplex GoComplex64;
|
|
54
|
+
typedef _Dcomplex GoComplex128;
|
|
55
|
+
#else
|
|
56
|
+
typedef float _Complex GoComplex64;
|
|
57
|
+
typedef double _Complex GoComplex128;
|
|
58
|
+
#endif
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
static assertion to make sure the file is being used on architecture
|
|
62
|
+
at least with matching size of GoInt.
|
|
63
|
+
*/
|
|
64
|
+
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
|
65
|
+
|
|
66
|
+
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
|
67
|
+
typedef _GoString_ GoString;
|
|
68
|
+
#endif
|
|
69
|
+
typedef void *GoMap;
|
|
70
|
+
typedef void *GoChan;
|
|
71
|
+
typedef struct { void *t; void *v; } GoInterface;
|
|
72
|
+
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
|
73
|
+
|
|
74
|
+
#endif
|
|
75
|
+
|
|
76
|
+
/* End of boilerplate cgo prologue. */
|
|
77
|
+
|
|
78
|
+
#ifdef __cplusplus
|
|
79
|
+
extern "C" {
|
|
80
|
+
#endif
|
|
81
|
+
|
|
82
|
+
extern char* InitClient(char* jsonOptions);
|
|
83
|
+
extern char* SubmitFeedback(char* jsonPayload);
|
|
84
|
+
extern char* Activate(char* jsonRequest);
|
|
85
|
+
extern char* CheckActivation(char* jsonRequest);
|
|
86
|
+
extern void FreeString(char* ptr);
|
|
87
|
+
|
|
88
|
+
#ifdef __cplusplus
|
|
89
|
+
}
|
|
90
|
+
#endif
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@howuse/feedback",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node.js/Electron SDK for user feedback and activation management",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build:native": "cd native-sdk && bash build.sh",
|
|
11
|
+
"build:js": "vite build && tsc --emitDeclarationOnly --outDir dist",
|
|
12
|
+
"build": "npm run build:native && npm run build:js",
|
|
13
|
+
"lint": "eslint src --ext .ts",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"feedback",
|
|
18
|
+
"activation",
|
|
19
|
+
"sdk",
|
|
20
|
+
"electron"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"koffi": "^2.8.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
29
|
+
"typescript": "^5.0.0",
|
|
30
|
+
"vite": "^5.0.0"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"native",
|
|
35
|
+
"README.md"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
|