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 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,8 @@
1
+ import { YiChongCanvaProvider } from "./login/privider.js";
2
+ import { YiChongCanvaContext } from "./login/context.js";
3
+ import { useYiChongCanva } from "./login/use_canva.js";
4
+ export {
5
+ YiChongCanvaContext,
6
+ YiChongCanvaProvider,
7
+ useYiChongCanva
8
+ };
@@ -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,6 @@
1
+ const requestUrl = (option) => {
2
+ return fetch(option.url, option).then((res) => res.json());
3
+ };
4
+ export {
5
+ requestUrl as default
6
+ };
@@ -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
+ };