yichong-canva2 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 +65 -0
- package/dist/index.js +8 -0
- package/dist/login/context.js +36 -0
- package/dist/login/login/apis/index.js +78 -0
- package/dist/login/login/apis/request.js +6 -0
- package/dist/login/login/login.js +190 -0
- package/dist/login/privider.js +603 -0
- package/dist/login/use_canva.js +12 -0
- package/dist/login/version.js +26 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/login/context.d.ts +76 -0
- package/dist/types/login/index.d.ts +4 -0
- package/dist/types/login/interface.d.ts +72 -0
- package/dist/types/login/login/apis/index.d.ts +32 -0
- package/dist/types/login/login/apis/request.d.ts +9 -0
- package/dist/types/login/login/interface.d.ts +16 -0
- package/dist/types/login/login/login.d.ts +77 -0
- package/dist/types/login/privider.d.ts +38 -0
- package/dist/types/login/use_canva.d.ts +6 -0
- package/dist/types/login/version.d.ts +4 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# yichong-canva-provider
|
|
2
|
+
|
|
3
|
+
A React provider package for Canva integration, managing user authentication, subscription status, and profile information.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- User authentication management
|
|
8
|
+
- Subscription status tracking
|
|
9
|
+
- Profile information handling
|
|
10
|
+
- Google Drive integration
|
|
11
|
+
- Canva token management
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install yichong-canva2
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
This package requires the following peer dependencies:
|
|
22
|
+
- React >= 18
|
|
23
|
+
- React DOM >= 18
|
|
24
|
+
- @canva/user >= 2.1.0
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { YichongCanvaProvider } from 'yichong-canva2';
|
|
30
|
+
|
|
31
|
+
function App() {
|
|
32
|
+
return (
|
|
33
|
+
<YichongCanvaProvider>
|
|
34
|
+
{/* Your app components */}
|
|
35
|
+
</YichongCanvaProvider>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## TypeScript Support
|
|
41
|
+
|
|
42
|
+
This package is written in TypeScript and includes type definitions. The main types include:
|
|
43
|
+
|
|
44
|
+
- `UserStatus`: Interface for user authentication and profile data
|
|
45
|
+
- `Subscription`: User subscription details
|
|
46
|
+
|
|
47
|
+
## Scripts
|
|
48
|
+
|
|
49
|
+
- `npm run dev`: Start development server
|
|
50
|
+
- `npm run build`: Build the package
|
|
51
|
+
- `npm run lint`: Run ESLint
|
|
52
|
+
- `npm run preview`: Preview the build
|
|
53
|
+
- `npm run publish`: Publish the package
|
|
54
|
+
|
|
55
|
+
## Build Configuration
|
|
56
|
+
|
|
57
|
+
The package uses Vite for building and includes:
|
|
58
|
+
- TypeScript support
|
|
59
|
+
- React plugin
|
|
60
|
+
- DTS plugin for type definitions
|
|
61
|
+
- Terser for minification
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
This project is proprietary and confidential.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
const YiChongCanvaContext = React.createContext(
|
|
3
|
+
{
|
|
4
|
+
userStatus: {},
|
|
5
|
+
updateUserStatus: () => {
|
|
6
|
+
},
|
|
7
|
+
handleLogin: () => {
|
|
8
|
+
},
|
|
9
|
+
credits: 0,
|
|
10
|
+
getLimitUsage: () => null,
|
|
11
|
+
getMonthlyUsageLimit: () => null,
|
|
12
|
+
isSubscribed: false,
|
|
13
|
+
use: () => {
|
|
14
|
+
},
|
|
15
|
+
canUse: () => false,
|
|
16
|
+
canUseLimit: () => ({ canUse: false }),
|
|
17
|
+
isFreeUnlimited: () => false,
|
|
18
|
+
pollUserStatus: () => void 0,
|
|
19
|
+
getPricingLink: () => "",
|
|
20
|
+
updateTestMode: () => {
|
|
21
|
+
},
|
|
22
|
+
refreshUserStatus: () => Promise.resolve({}),
|
|
23
|
+
hasClickedUpgrade: false,
|
|
24
|
+
setHasClickedUpgrade: () => {
|
|
25
|
+
},
|
|
26
|
+
stopCurrentPolling: () => false,
|
|
27
|
+
updateCredits: () => {
|
|
28
|
+
},
|
|
29
|
+
loginStatus: "idle",
|
|
30
|
+
manualLogin: () => {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
export {
|
|
35
|
+
YiChongCanvaContext
|
|
36
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import requestUrl from "./request.js";
|
|
2
|
+
const headers = {
|
|
3
|
+
"Content-Type": "application/json"
|
|
4
|
+
};
|
|
5
|
+
const baseUrl = "https://usa.imgkits.com/";
|
|
6
|
+
function getUserIds(token, appId) {
|
|
7
|
+
const queryString = new URLSearchParams({
|
|
8
|
+
canva_user_token: token,
|
|
9
|
+
appid: appId
|
|
10
|
+
}).toString();
|
|
11
|
+
const url = `https://canva.livepolls.app/getUserIdByAppid?${queryString}`;
|
|
12
|
+
return requestUrl({
|
|
13
|
+
url,
|
|
14
|
+
method: "GET",
|
|
15
|
+
headers,
|
|
16
|
+
redirect: "follow"
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function handleUserLoginByCanvaId(userId, productName) {
|
|
20
|
+
const url = baseUrl + productName + "/api/user/canvaid-login";
|
|
21
|
+
return requestUrl({
|
|
22
|
+
url,
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers,
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
canva_id: userId
|
|
27
|
+
}),
|
|
28
|
+
redirect: "follow"
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function getPassport(userId, productName) {
|
|
32
|
+
const passport = localStorage.getItem(userId + "_passport");
|
|
33
|
+
if (passport) {
|
|
34
|
+
return Promise.resolve({
|
|
35
|
+
data: {
|
|
36
|
+
passport
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
let url = baseUrl + productName + "/api/user/canva-passport";
|
|
41
|
+
if (productName === "imgkits") {
|
|
42
|
+
url = baseUrl + "/api/user/canva-passport";
|
|
43
|
+
}
|
|
44
|
+
return requestUrl({
|
|
45
|
+
url,
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers,
|
|
48
|
+
body: JSON.stringify({
|
|
49
|
+
canva_id: userId
|
|
50
|
+
}),
|
|
51
|
+
redirect: "follow"
|
|
52
|
+
}).then((res) => {
|
|
53
|
+
localStorage.setItem(userId + "_passport", res.data.passport);
|
|
54
|
+
return res;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function getUserStatusByPassport(passport, productName) {
|
|
58
|
+
const header = {
|
|
59
|
+
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
60
|
+
"X-Access-Ticket": passport
|
|
61
|
+
};
|
|
62
|
+
const url = baseUrl + productName + "/api/user/status";
|
|
63
|
+
return requestUrl({
|
|
64
|
+
url,
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: header,
|
|
67
|
+
redirect: "follow"
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const apis = {
|
|
71
|
+
getUserIds,
|
|
72
|
+
handleUserLoginByCanvaId,
|
|
73
|
+
getPassport,
|
|
74
|
+
getUserStatusByPassport
|
|
75
|
+
};
|
|
76
|
+
export {
|
|
77
|
+
apis as default
|
|
78
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { auth } from "@canva/user";
|
|
2
|
+
import apis from "./apis/index.js";
|
|
3
|
+
auth.initOauth();
|
|
4
|
+
async function handleLogin({
|
|
5
|
+
appId,
|
|
6
|
+
productName
|
|
7
|
+
}) {
|
|
8
|
+
try {
|
|
9
|
+
let userId = window.userId;
|
|
10
|
+
console.log("userId", userId);
|
|
11
|
+
if (!userId) {
|
|
12
|
+
const token = await auth.getCanvaUserToken();
|
|
13
|
+
window.g_canva_token = token;
|
|
14
|
+
const userIdResult = await apis.getUserIds(token, appId);
|
|
15
|
+
userId = userIdResult.verified.userId;
|
|
16
|
+
window.userId = userId;
|
|
17
|
+
await apis.handleUserLoginByCanvaId(userId, productName);
|
|
18
|
+
}
|
|
19
|
+
const userStatus = await getUserProfile({
|
|
20
|
+
userId,
|
|
21
|
+
productName
|
|
22
|
+
});
|
|
23
|
+
window.g_token = userStatus.jwt || "";
|
|
24
|
+
userStatus.userId = userId;
|
|
25
|
+
return userStatus;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
throw new Error("login failed");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function takeSubscribeBaseline(userStatus) {
|
|
31
|
+
const sub = userStatus == null ? void 0 : userStatus.subscription;
|
|
32
|
+
return {
|
|
33
|
+
free_cnt: typeof (sub == null ? void 0 : sub.free_cnt) === "number" ? sub.free_cnt : null,
|
|
34
|
+
detailJson: (sub == null ? void 0 : sub.detail) == null ? null : JSON.stringify(sub.detail),
|
|
35
|
+
available: computeAvailableLimit(userStatus)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function computeAvailableLimit(userStatus) {
|
|
39
|
+
var _a, _b;
|
|
40
|
+
const sub = userStatus == null ? void 0 : userStatus.subscription;
|
|
41
|
+
const freeCnt = typeof (sub == null ? void 0 : sub.free_cnt) === "number" ? Math.max(sub.free_cnt, 0) : 0;
|
|
42
|
+
const detail = sub == null ? void 0 : sub.detail;
|
|
43
|
+
const isActive = !!detail && (!detail.ends_at || new Date(detail.ends_at).getTime() > Date.now());
|
|
44
|
+
if (!isActive) {
|
|
45
|
+
return freeCnt;
|
|
46
|
+
}
|
|
47
|
+
const limit = ((_b = (_a = userStatus.profile) == null ? void 0 : _a.usage_limits) == null ? void 0 : _b.monthly_usage_limit) ?? 0;
|
|
48
|
+
if (limit <= -1) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const used = (sub == null ? void 0 : sub.monthly_usage_cnt) ?? 0;
|
|
52
|
+
return Math.max(limit - used, 0) + freeCnt;
|
|
53
|
+
}
|
|
54
|
+
function isSubscribeChanged(current, baseline) {
|
|
55
|
+
const sub = current.subscription;
|
|
56
|
+
const detailJson = (sub == null ? void 0 : sub.detail) == null ? null : JSON.stringify(sub.detail);
|
|
57
|
+
if (detailJson !== baseline.detailJson) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
const freeCnt = typeof (sub == null ? void 0 : sub.free_cnt) === "number" ? sub.free_cnt : null;
|
|
61
|
+
if (freeCnt != null && (baseline.free_cnt == null ? freeCnt > 0 : freeCnt > baseline.free_cnt)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
const available = computeAvailableLimit(current);
|
|
65
|
+
if (available != null && baseline.available != null && available > baseline.available) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 轮询获取用户资料/订阅状态
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} options - 参数对象
|
|
74
|
+
* @param {number} [options.maxCount=50] - 最大轮询次数
|
|
75
|
+
* @param {number|function} [options.interval] - 每次轮询等待的间隔(ms),可以是函数
|
|
76
|
+
* @param {number} [options.maxDurationMs] - 最大轮询持续时间(ms)
|
|
77
|
+
* @param {string} options.productName - 产品名称
|
|
78
|
+
* @param {string} options.userId - 用户ID
|
|
79
|
+
* @param {function} [options.successCallback] - 轮询成功(获取到所需状态)时的回调
|
|
80
|
+
* @param {function} [options.failCallback] - 轮询失败时的回调
|
|
81
|
+
* @param {boolean} [options.isSubscribe=false] - 是否为订阅状态检测
|
|
82
|
+
* @param {*} [options.baseline] - 比较基准,用于判断订阅状态是否变化
|
|
83
|
+
* @param {function} [options.isChanged] - 判断状态变化的函数
|
|
84
|
+
* @param {function} [options.onStop] - 停止轮询回调(暴露终止方法)
|
|
85
|
+
* @returns {Promise<object>} 用户状态对象
|
|
86
|
+
*/
|
|
87
|
+
async function getUserProfile({
|
|
88
|
+
maxCount = 50,
|
|
89
|
+
interval,
|
|
90
|
+
maxDurationMs,
|
|
91
|
+
productName,
|
|
92
|
+
userId,
|
|
93
|
+
successCallback,
|
|
94
|
+
failCallback,
|
|
95
|
+
isSubscribe = false,
|
|
96
|
+
baseline,
|
|
97
|
+
isChanged,
|
|
98
|
+
onStop
|
|
99
|
+
}) {
|
|
100
|
+
var _a, _b;
|
|
101
|
+
let count = 0; // 已轮询次数
|
|
102
|
+
const startedAt = Date.now(); // 记录开始时间
|
|
103
|
+
// 获取passport凭证
|
|
104
|
+
let passportResult = await apis.getPassport(userId, productName);
|
|
105
|
+
let passport = (_a = passportResult.data) == null ? void 0 : _a.passport;
|
|
106
|
+
let isStop = false; // 外部停止标识
|
|
107
|
+
let cancelSleep = null; // 终止sleep回调
|
|
108
|
+
let snapshot = baseline ?? null; // 订阅基线快照
|
|
109
|
+
// 收到onStop通知时,终止轮询
|
|
110
|
+
onStop == null ? void 0 : onStop(() => {
|
|
111
|
+
isStop = true;
|
|
112
|
+
count = maxCount; // 直接达到退出条件
|
|
113
|
+
cancelSleep == null ? void 0 : cancelSleep(); // 若在sleep中则提前唤醒
|
|
114
|
+
});
|
|
115
|
+
// 判断是否到达最大轮询次数或超时
|
|
116
|
+
const expired = () => count >= maxCount || maxDurationMs != null && Date.now() - startedAt >= maxDurationMs;
|
|
117
|
+
while (!expired()) {
|
|
118
|
+
console.log("poll", count);
|
|
119
|
+
let userStatus;
|
|
120
|
+
try {
|
|
121
|
+
// 查询passport对应的用户状态
|
|
122
|
+
userStatus = await (await apis.getUserStatusByPassport(passport, productName)).data;
|
|
123
|
+
} catch (e) {
|
|
124
|
+
userStatus = void 0;
|
|
125
|
+
}
|
|
126
|
+
if (userStatus == null ? void 0 : userStatus.profile) {
|
|
127
|
+
// 普通获取,无需订阅状态变化判断
|
|
128
|
+
if (!isSubscribe) {
|
|
129
|
+
successCallback == null ? void 0 : successCallback(userStatus);
|
|
130
|
+
return userStatus;
|
|
131
|
+
}
|
|
132
|
+
// 订阅状态获取
|
|
133
|
+
if (!snapshot) {
|
|
134
|
+
snapshot = takeSubscribeBaseline(userStatus); // 初始化基线
|
|
135
|
+
} else {
|
|
136
|
+
// 用isChanged或缺省方法进行状态对比
|
|
137
|
+
const changed = isChanged ? isChanged(userStatus, snapshot) : isSubscribeChanged(userStatus, snapshot);
|
|
138
|
+
if (changed) {
|
|
139
|
+
console.log("subscribe success");
|
|
140
|
+
successCallback == null ? void 0 : successCallback(userStatus);
|
|
141
|
+
return userStatus;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} else if (userStatus) {
|
|
145
|
+
// (有返回但无profile) passport失效,移除缓存重新获取
|
|
146
|
+
localStorage.removeItem(userId + "_passport");
|
|
147
|
+
try {
|
|
148
|
+
passportResult = await apis.getPassport(userId, productName);
|
|
149
|
+
passport = (_b = passportResult.data) == null ? void 0 : _b.passport;
|
|
150
|
+
} catch (e) {
|
|
151
|
+
// 无需处理,继续重试
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
count++;
|
|
155
|
+
if (isStop || expired()) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
// 计算下一次轮询的间隔
|
|
159
|
+
let ms = typeof interval === "function" ? interval() : interval ?? 3e3;
|
|
160
|
+
// 距离最大允许持续时间的剩余时间也必须纳入约束
|
|
161
|
+
if (maxDurationMs != null) {
|
|
162
|
+
ms = Math.min(ms, Math.max(maxDurationMs - (Date.now() - startedAt), 0));
|
|
163
|
+
}
|
|
164
|
+
await sleep(ms, (cancel) => {
|
|
165
|
+
cancelSleep = cancel;
|
|
166
|
+
});
|
|
167
|
+
cancelSleep = null;
|
|
168
|
+
}
|
|
169
|
+
if (isStop) {
|
|
170
|
+
return; // 被外部主动终止,直接返回
|
|
171
|
+
}
|
|
172
|
+
failCallback == null ? void 0 : failCallback();
|
|
173
|
+
throw new Error("get user profile error");
|
|
174
|
+
}
|
|
175
|
+
function sleep(ms = 3e3, onCancel) {
|
|
176
|
+
return new Promise((resolve) => {
|
|
177
|
+
const id = setTimeout(resolve, ms);
|
|
178
|
+
onCancel == null ? void 0 : onCancel(() => {
|
|
179
|
+
clearTimeout(id);
|
|
180
|
+
resolve();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
export {
|
|
185
|
+
computeAvailableLimit,
|
|
186
|
+
getUserProfile,
|
|
187
|
+
handleLogin,
|
|
188
|
+
isSubscribeChanged,
|
|
189
|
+
takeSubscribeBaseline
|
|
190
|
+
};
|