@arcblock/did-connect-react 3.5.1 → 3.5.2

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.
@@ -23,7 +23,7 @@ import _e from "@arcblock/ux/lib/DID";
23
23
  import Ee from "@arcblock/ux/lib/Avatar";
24
24
  import { getCurrentAppPid as dt } from "@arcblock/ux/lib/SessionUser/libs/utils";
25
25
  import { translate as pt } from "@arcblock/ux/lib/Locale/util";
26
- import { getBlockletSDK as ft } from "@blocklet/js-sdk";
26
+ import { getBlockletSDK as ft } from "../node_modules/@blocklet/js-sdk/dist/index.js";
27
27
  import b from "@arcblock/bridge";
28
28
  import { getFederatedEnabled as ut } from "@arcblock/ux/lib/Util/federated";
29
29
  import wt from "lodash/noop";
@@ -0,0 +1,512 @@
1
+ import { SESSION_TOKEN_STORAGE_KEY as g, REFRESH_TOKEN_STORAGE_KEY as y, WELLKNOWN_SERVICE_PATH_PREFIX as w } from "@abtnode/constant";
2
+ import { joinURL as k, withQuery as S } from "ufo";
3
+ import p from "js-cookie";
4
+ import L from "quick-lru";
5
+ import B from "lodash/isEmpty";
6
+ import C from "axios";
7
+ import x from "keyv";
8
+ import { KeyvLocalStorage as A } from "keyv-browser";
9
+ import F from "lodash/isNumber";
10
+ import j from "lodash/omit";
11
+ import N from "lodash/isObject";
12
+ import $ from "json-stable-stringify";
13
+ import "is-url";
14
+ var K = class {
15
+ constructor({ api: e, token: t }) {
16
+ this.api = e, this.token = t;
17
+ }
18
+ async getUserPublicInfo({ did: e, name: t }) {
19
+ const { data: o } = await this.api.get("/api/user", { params: {
20
+ did: e,
21
+ name: t
22
+ } });
23
+ return o;
24
+ }
25
+ async getUserPrivacyConfig({ did: e, name: t }) {
26
+ const { data: o } = await this.api.get("/api/user/privacy/config", { params: {
27
+ did: e,
28
+ name: t
29
+ } });
30
+ return o;
31
+ }
32
+ async saveUserPrivacyConfig(e) {
33
+ const { data: t } = await this.api.post("/api/user/privacy/config", e);
34
+ return t;
35
+ }
36
+ async getUserNotificationConfig() {
37
+ const { data: e } = await this.api.get("/api/user/notification/config");
38
+ return e;
39
+ }
40
+ async saveUserNotificationConfig(e) {
41
+ const { data: t } = await this.api.post("/api/user/notification/config", e);
42
+ return t;
43
+ }
44
+ async testNotificationWebhook(e) {
45
+ const { data: t } = await this.api.put("/api/user/notification/webhook", e);
46
+ return t;
47
+ }
48
+ async getProfileUrl({ did: e, locale: t }) {
49
+ return S(`${w}/user`, {
50
+ did: e,
51
+ locale: t
52
+ });
53
+ }
54
+ async getProfile() {
55
+ const { data: e } = await this.api.get("/api/user/profile");
56
+ return e;
57
+ }
58
+ async refreshProfile() {
59
+ await this.api.put("/api/user/refreshProfile");
60
+ }
61
+ async followUser({ userDid: e }) {
62
+ await this.api.post(`/api/user/follow/${e}`);
63
+ }
64
+ async unfollowUser({ userDid: e }) {
65
+ await this.api.delete(`/api/user/follow/${e}`);
66
+ }
67
+ async isFollowingUser({ userDid: e }) {
68
+ const { data: t } = await this.api.get(`/api/user/follow/${e}`);
69
+ return t.isFollowing;
70
+ }
71
+ async saveProfile({ locale: e, inviter: t, metadata: o, address: n }) {
72
+ const { data: r } = await this.api.put("/api/user/profile", {
73
+ locale: e,
74
+ inviter: t,
75
+ metadata: o,
76
+ address: n
77
+ });
78
+ return r;
79
+ }
80
+ async updateDidSpace({ spaceGateway: e }) {
81
+ await this.api.put("/api/user/updateDidSpace", { spaceGateway: e });
82
+ }
83
+ /**
84
+ * 指定要退出登录的设备 id
85
+ * 指定要退出登录的会话状态
86
+ * @param {{ visitorId: string, status: string }} { visitorId, status }
87
+ * @return {Promise<void>}
88
+ */
89
+ async logout({ visitorId: e, status: t, includeFederated: o }) {
90
+ const n = this.token.getRefreshToken(), { data: r } = await this.api.post("/api/user/logout", {
91
+ visitorId: e,
92
+ status: t,
93
+ includeFederated: o,
94
+ refreshToken: n
95
+ });
96
+ return r;
97
+ }
98
+ /**
99
+ * 删除当前登录用户
100
+ * @return {Promise<{did: string}>}
101
+ */
102
+ async destroyMyself() {
103
+ const { data: e } = await this.api.delete("/api/user");
104
+ return e;
105
+ }
106
+ /**
107
+ * org 相关
108
+ */
109
+ async getOrgs({ search: e = "", type: t = "", page: o = 1, pageSize: n = 20 }) {
110
+ const { data: r } = await this.api.get("/api/user/orgs", { params: {
111
+ search: e,
112
+ page: o,
113
+ pageSize: n,
114
+ type: t
115
+ } });
116
+ return r;
117
+ }
118
+ async getOrg(e) {
119
+ const { data: t } = await this.api.get(`/api/user/orgs/${e}`);
120
+ return t;
121
+ }
122
+ async createOrg(e) {
123
+ const { data: t } = await this.api.post("/api/user/orgs", { org: e });
124
+ return t;
125
+ }
126
+ async getRole(e) {
127
+ const { data: t } = await this.api.get("/api/user/role", { params: { name: e } });
128
+ return t;
129
+ }
130
+ async addResourceToOrg({ orgId: e, resourceId: t, type: o, metadata: n }) {
131
+ const { data: r } = await this.api.post(`/api/user/orgs/${e}/resources`, {
132
+ resourceId: t,
133
+ type: o,
134
+ metadata: n
135
+ });
136
+ return r;
137
+ }
138
+ async migrateResourceToOrg({ form: e, to: t, resourceId: o }) {
139
+ const { data: n } = await this.api.put(`/api/user/orgs/${e}/resources`, {
140
+ to: t,
141
+ resourceId: o
142
+ });
143
+ return n;
144
+ }
145
+ }, D = class {
146
+ getSessionToken(e) {
147
+ return p.get(g) ? p.get(g) : e.sessionTokenKey ? window.localStorage.getItem(e.sessionTokenKey) : "";
148
+ }
149
+ setSessionToken(e) {
150
+ p.set(g, e);
151
+ }
152
+ removeSessionToken() {
153
+ p.remove(g);
154
+ }
155
+ getRefreshToken() {
156
+ return localStorage.getItem(y);
157
+ }
158
+ setRefreshToken(e) {
159
+ localStorage.setItem(y, e);
160
+ }
161
+ removeRefreshToken() {
162
+ localStorage.removeItem(y);
163
+ }
164
+ };
165
+ let T;
166
+ function O() {
167
+ return T || (T = new L({
168
+ maxSize: 30,
169
+ maxAge: 60 * 1e3
170
+ })), T;
171
+ }
172
+ var b = class {
173
+ getBlocklet(e, t = !1) {
174
+ if (!e) {
175
+ if (typeof window > "u" || typeof document > "u") throw new Error("Cannot get blocklet in server side without baseUrl");
176
+ return window.blocklet;
177
+ }
178
+ const o = O();
179
+ if (!t && o.has(e)) return o.get(e);
180
+ const n = S(k(e, "__blocklet__.js"), {
181
+ type: "json",
182
+ t: Date.now()
183
+ });
184
+ return new Promise(async (r) => {
185
+ const s = await (await fetch(n)).json();
186
+ o.set(e, s), r(s);
187
+ });
188
+ }
189
+ loadBlocklet() {
190
+ return new Promise((e, t) => {
191
+ if (typeof window > "u" || typeof document > "u") {
192
+ t();
193
+ return;
194
+ }
195
+ const o = document.createElement("script");
196
+ let n = "/";
197
+ window.blocklet && window.blocklet.prefix && (n = window.blocklet.prefix), o.src = S(k(n, "__blocklet__.js"), { t: Date.now() }), o.onload = () => {
198
+ e();
199
+ }, o.onerror = () => {
200
+ t();
201
+ }, document.head.append(o);
202
+ });
203
+ }
204
+ getPrefix(e) {
205
+ return e ? e?.prefix || "/" : typeof window > "u" || typeof document > "u" ? null : window.blocklet?.prefix || "/";
206
+ }
207
+ }, M = class {
208
+ constructor({ api: e, blocklet: t }) {
209
+ this.api = e, this.blocklet = t || new b();
210
+ }
211
+ getBaseUrl(e) {
212
+ return e ? k(e, w) : void 0;
213
+ }
214
+ async getUserSessions({ did: e, appUrl: t }) {
215
+ const o = this.getBaseUrl(t), n = await this.blocklet.getBlocklet(), { data: r } = await this.api.get("/api/user-session", {
216
+ baseURL: o,
217
+ params: {
218
+ userDid: e,
219
+ appPid: n.appPid
220
+ }
221
+ });
222
+ return r;
223
+ }
224
+ /**
225
+ * 获取个人的所有登录会话
226
+ */
227
+ async getMyLoginSessions({ appUrl: e } = {}, t = {
228
+ page: 1,
229
+ pageSize: 10
230
+ }) {
231
+ const o = this.getBaseUrl(e), { data: n } = await this.api.get("/api/user-session/myself", {
232
+ baseURL: o,
233
+ params: t
234
+ });
235
+ return n;
236
+ }
237
+ async loginByUserSession({ id: e, appPid: t, userDid: o, passportId: n, appUrl: r }) {
238
+ const s = this.getBaseUrl(r), { data: d } = await this.api.post("/api/user-session/login", {
239
+ id: e,
240
+ appPid: t,
241
+ userDid: o,
242
+ passportId: n
243
+ }, { baseURL: s });
244
+ return d;
245
+ }
246
+ }, z = class {
247
+ constructor({ blocklet: e = window?.blocklet } = {}) {
248
+ this.blocklet = e;
249
+ }
250
+ getComponent(e) {
251
+ return (this.blocklet?.componentMountPoints || []).find((t) => [
252
+ t.title,
253
+ t.name,
254
+ t.did
255
+ ].includes(e));
256
+ }
257
+ getComponentMountPoint(e) {
258
+ return this.getComponent(e)?.mountPoint || "";
259
+ }
260
+ getUrl(e, ...t) {
261
+ const o = this.getComponentMountPoint(e);
262
+ return k(this.blocklet?.appUrl || "", o, ...t);
263
+ }
264
+ }, W = class {
265
+ constructor({ api: e, blocklet: t }) {
266
+ this.blockletDataCache = {}, this.api = e, this.blocklet = t || new b();
267
+ }
268
+ async getTrustedDomains() {
269
+ const { data: e } = await this.api.get("/api/federated/getTrustedDomains");
270
+ return e;
271
+ }
272
+ getMaster(e = this.blocklet.getBlocklet()) {
273
+ return e?.settings?.federated?.master;
274
+ }
275
+ getConfig(e = this.blocklet.getBlocklet()) {
276
+ return e?.settings?.federated?.config;
277
+ }
278
+ getFederatedEnabled(e = this.blocklet.getBlocklet()) {
279
+ return this.getConfig(e)?.status === "approved";
280
+ }
281
+ getSourceAppPid(e = this.blocklet.getBlocklet()) {
282
+ return this.getMaster(e)?.appPid;
283
+ }
284
+ getFederatedApp(e = this.blocklet.getBlocklet()) {
285
+ const t = this.getMaster(e);
286
+ return B(t) ? null : {
287
+ appId: t.appId,
288
+ appName: t.appName,
289
+ appDescription: t.appDescription,
290
+ appLogo: t.appLogo,
291
+ appPid: t.appPid,
292
+ appUrl: t.appUrl,
293
+ version: t.version,
294
+ sourceAppPid: t.appPid,
295
+ provider: "wallet"
296
+ };
297
+ }
298
+ getCurrentApp(e = this.blocklet.getBlocklet()) {
299
+ if (e) return {
300
+ appId: e.appId,
301
+ appName: e.appName,
302
+ appDescription: e.appDescription,
303
+ appLogo: e.appLogo,
304
+ appPid: e.appPid,
305
+ appUrl: e.appUrl,
306
+ version: e.version,
307
+ sourceAppPid: null,
308
+ provider: "wallet"
309
+ };
310
+ if (window.env) {
311
+ const t = window.env;
312
+ return {
313
+ appId: t.appId,
314
+ appName: t.appName,
315
+ appDescription: t.appDescription,
316
+ appUrl: t.baseUrl,
317
+ sourceAppPid: null,
318
+ provider: "wallet",
319
+ type: "server"
320
+ };
321
+ }
322
+ return null;
323
+ }
324
+ getApps(e = this.blocklet.getBlocklet()) {
325
+ const t = [], o = this.getFederatedApp(e), n = this.getCurrentApp(e), r = this.getFederatedEnabled(e);
326
+ return n && t.push(n), o && o?.appId !== n?.appId && r && t.push(o), t.reverse();
327
+ }
328
+ async getBlockletData(e, t = !1) {
329
+ if (!t && this.blockletDataCache[e]) return this.blockletDataCache[e];
330
+ try {
331
+ const o = new URL("__blocklet__.js", e);
332
+ o.searchParams.set("type", "json");
333
+ const n = await (await fetch(o.href)).json();
334
+ return this.blockletDataCache[e] = n, n;
335
+ } catch (o) {
336
+ return console.error(`Failed to get blocklet data: ${e}`, o), null;
337
+ }
338
+ }
339
+ }, q = "1.17.8";
340
+ const G = (e = 0) => new Promise((t) => {
341
+ setTimeout(() => {
342
+ t();
343
+ }, e);
344
+ }), U = (e) => `Bearer ${encodeURIComponent(e)}`, H = "vid", Q = "__visitor_id", V = () => p.get(H) || localStorage.getItem(Q), Y = async (e, t, o) => {
345
+ if (N(e.data) && e.status >= 200 && e.status < 300 && typeof window < "u" && window.blocklet?.appId && window.blocklet?.appPk) {
346
+ if (!o) return e;
347
+ if (!e.data.$signature)
348
+ throw t(), new Error("Invalid response");
349
+ const { appId: n, appPk: r } = window.blocklet;
350
+ if (await o($(j(e.data, ["$signature"])), e.data.$signature, r, n) === !1)
351
+ throw t(), new Error("Invalid response");
352
+ }
353
+ return e;
354
+ };
355
+ function R() {
356
+ return p.get("x-csrf-token");
357
+ }
358
+ function _() {
359
+ return p.get("login_token");
360
+ }
361
+ async function X() {
362
+ const e = R();
363
+ try {
364
+ const t = k(window.location.origin, w, "/api/did/csrfToken"), { data: o } = await C.get(t, { headers: { "x-csrf-token": e } });
365
+ return o;
366
+ } catch (t) {
367
+ return console.error(t), {
368
+ loginToken: _(),
369
+ csrfToken: null
370
+ };
371
+ }
372
+ }
373
+ let v;
374
+ function J() {
375
+ if (!v) {
376
+ const e = window?.blocklet?.settings?.session?.cacheTtl;
377
+ v = new x({
378
+ store: new A(),
379
+ ttl: F(e) ? e * 1e3 : 1e3 * 60 * 60
380
+ });
381
+ }
382
+ return v;
383
+ }
384
+ async function P(e, t = 300) {
385
+ e.metaData.endTime = +/* @__PURE__ */ new Date();
386
+ const { startTime: o, endTime: n } = e.metaData, r = n - o;
387
+ r < t && await G(t - r), delete e.metaData;
388
+ }
389
+ const I = (e, t) => {
390
+ const o = {
391
+ ...e?.headers,
392
+ "x-blocklet-js-sdk-version": q
393
+ }, n = new z(), r = C.create({
394
+ ...e,
395
+ headers: o
396
+ });
397
+ return t?.lazy && (r.interceptors.request.use((s) => (s.metaData = { startTime: +/* @__PURE__ */ new Date() }, s), (s) => Promise.reject(s)), r.interceptors.response.use(async (s) => (s.config && await P(s.config, t?.lazyTime), s), async (s) => (s.response && await P(s.response.config, t?.lazyTime), Promise.reject(s)))), r.interceptors.request.use(async (s) => {
398
+ const d = t?.componentDid ?? window.blocklet?.componentId?.split("/").pop();
399
+ s.baseURL = s.baseURL || n.getComponentMountPoint(d), s.timeout = s.timeout || 20 * 1e3;
400
+ const f = _(), m = R();
401
+ if (f && m) {
402
+ const l = f.slice(-32), u = J(), i = await u.get(l);
403
+ if (i) s.headers["x-csrf-token"] = i;
404
+ else {
405
+ const { loginToken: a, csrfToken: c } = await X();
406
+ c ? (await u.set(a.slice(-32), c), s.headers["x-csrf-token"] = c) : s.headers["x-csrf-token"] = m;
407
+ }
408
+ s.headers["x-csrf-token"] && s.headers["x-csrf-token"] !== R() && p.set("x-csrf-token", s.headers["x-csrf-token"], {
409
+ sameSite: "strict",
410
+ secure: !0
411
+ });
412
+ }
413
+ const h = V();
414
+ return [void 0, null].includes(h) || (s.headers["x-blocklet-visitor-id"] = h), s;
415
+ }, (s) => Promise.reject(s)), r;
416
+ };
417
+ async function Z(e) {
418
+ if (!e) throw new Error("Refresh token not found");
419
+ const { data: t } = await I({
420
+ baseURL: w,
421
+ timeout: 10 * 1e3,
422
+ secure: !0,
423
+ headers: { authorization: U(e) }
424
+ }).post("/api/did/refreshSession");
425
+ return t;
426
+ }
427
+ function E({ getSessionToken: e, setSessionToken: t, removeSessionToken: o, getRefreshToken: n, setRefreshToken: r, removeRefreshToken: s, onRefreshTokenError: d, onRefreshTokenSuccess: f }, m, h) {
428
+ let l = null;
429
+ const u = I({
430
+ timeout: 30 * 1e3,
431
+ ...m
432
+ }, h);
433
+ return u.interceptors.request.use(async (i) => {
434
+ if (!p.get(g)) {
435
+ const a = e(i);
436
+ a && (i.headers.authorization = U(a));
437
+ }
438
+ return l && await l, i;
439
+ }, (i) => Promise.reject(i)), u.interceptors.response.use(async (i) => i.config?.secure ? await Y(i, () => {
440
+ o(), s();
441
+ }, h?.verifyFn) : i, async (i) => {
442
+ const a = i.config;
443
+ if (a && (a.headers = a?.headers ? { ...a.headers } : {}, i?.response?.status === 401 && !a._retry)) {
444
+ a._retry = !0;
445
+ try {
446
+ l || (l = Z(n()));
447
+ const c = await l;
448
+ return t(c.nextToken), r(c.nextRefreshToken), typeof f == "function" && f(c), u(a);
449
+ } catch (c) {
450
+ return o(), s(), typeof d == "function" && d(c), Promise.reject(i);
451
+ } finally {
452
+ l = null;
453
+ }
454
+ }
455
+ return Promise.reject(i);
456
+ }), u;
457
+ }
458
+ var ee = class {
459
+ constructor(e) {
460
+ const t = new D(), o = E({
461
+ getSessionToken: t.getSessionToken,
462
+ setSessionToken: t.setSessionToken,
463
+ removeSessionToken: t.removeSessionToken,
464
+ getRefreshToken: t.getRefreshToken,
465
+ setRefreshToken: t.setRefreshToken,
466
+ removeRefreshToken: t.removeRefreshToken,
467
+ onRefreshTokenError: () => {
468
+ console.error("Failed to refresh token");
469
+ },
470
+ onRefreshTokenSuccess: () => {
471
+ }
472
+ }, { baseURL: w }, { verifyFn: e?.verifyFn }), n = new b();
473
+ this.user = new K({
474
+ api: o,
475
+ token: t
476
+ }), this.federated = new W({
477
+ api: o,
478
+ blocklet: n
479
+ }), this.userSession = new M({
480
+ api: o,
481
+ blocklet: n
482
+ }), this.token = t, this.blocklet = n, this.api = o;
483
+ }
484
+ };
485
+ function he(e = {}, t = {}) {
486
+ const o = new D();
487
+ return E({
488
+ getSessionToken: o.getSessionToken,
489
+ setSessionToken: o.setSessionToken,
490
+ removeSessionToken: o.removeSessionToken,
491
+ getRefreshToken: o.getRefreshToken,
492
+ setRefreshToken: o.setRefreshToken,
493
+ removeRefreshToken: o.removeRefreshToken,
494
+ onRefreshTokenError: () => {
495
+ console.error("Failed to refresh token");
496
+ },
497
+ onRefreshTokenSuccess: () => {
498
+ }
499
+ }, e, t);
500
+ }
501
+ const ge = /* @__PURE__ */ (() => {
502
+ let e;
503
+ return (t) => (e || (e = new ee(t)), e);
504
+ })();
505
+ export {
506
+ ee as BlockletSDK,
507
+ he as createAxios,
508
+ ge as getBlockletSDK,
509
+ R as getCSRFToken,
510
+ X as getCSRFTokenByLoginToken,
511
+ _ as getLoginToken
512
+ };
@@ -1,4 +1,4 @@
1
- const o = "3.5.1", s = {
1
+ const o = "3.5.2", s = {
2
2
  version: o
3
3
  };
4
4
  export {
package/lib/utils.js CHANGED
@@ -6,12 +6,12 @@ import c from "js-cookie";
6
6
  import l from "lodash/isNil";
7
7
  import T from "lodash/isString";
8
8
  import m from "lodash/isUndefined";
9
- import { createAxios as P, getBlockletSDK as x } from "@blocklet/js-sdk";
9
+ import { createAxios as P, getBlockletSDK as x } from "./node_modules/@blocklet/js-sdk/dist/index.js";
10
10
  import { withQuery as L } from "ufo";
11
11
  import { isValid as w, isFromPublicKey as S } from "@arcblock/did";
12
12
  import { getSafeUrl as C } from "@arcblock/ux/lib/Util/security";
13
13
  import { createDebug as u, createLogger as I } from "@arcblock/ux/lib/Util/logger";
14
- import N from "./package.json.js";
14
+ import N from "./packages/did-connect/package.json.js";
15
15
  import { DID_CONNECT_URL_PARAMS_NAME as f, DEFAULT_WINDOW_TIMEOUT as D } from "./constant.js";
16
16
  import { NotOpenError as W } from "./error.js";
17
17
  export * from "@arcblock/ux/lib/Util/wallet";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/did-connect-react",
3
- "version": "3.5.1",
3
+ "version": "3.5.2",
4
4
  "description": "Client side library to work with DID Connect by ArcBlock.",
5
5
  "keywords": [
6
6
  "react",
@@ -33,17 +33,17 @@
33
33
  "url": "https://github.com/ArcBlock/ux/issues"
34
34
  },
35
35
  "dependencies": {
36
- "@arcblock/bridge": "3.5.1",
37
- "@arcblock/did": "^1.29.4",
38
- "@arcblock/icons": "3.5.1",
39
- "@arcblock/react-hooks": "3.5.1",
40
- "@arcblock/ws": "^1.29.4",
41
- "@blocklet/constant": "^1.17.7",
36
+ "@arcblock/bridge": "3.5.2",
37
+ "@arcblock/did": "^1.29.8",
38
+ "@arcblock/icons": "3.5.2",
39
+ "@arcblock/react-hooks": "3.5.2",
40
+ "@arcblock/ws": "^1.29.8",
41
+ "@blocklet/constant": "^1.17.9",
42
42
  "@fontsource/lexend": "^5.2.9",
43
43
  "@iconify-icons/logos": "^1.2.36",
44
44
  "@iconify-icons/material-symbols": "^1.2.58",
45
45
  "@iconify/react": "^5.2.1",
46
- "@ocap/util": "^1.29.4",
46
+ "@ocap/util": "^1.29.8",
47
47
  "@simplewebauthn/browser": "^13.1.0",
48
48
  "ahooks": "^3.8.5",
49
49
  "axios": "^1.10.0",
@@ -90,5 +90,5 @@
90
90
  "eslint-plugin-react-hooks": "^4.6.2",
91
91
  "jest": "^29.7.0"
92
92
  },
93
- "gitHead": "78fdd8a869a60672e8b66d4eefc172c8a716acd8"
93
+ "gitHead": "2f6a2770fb95e0b8202de2973d9753be080260af"
94
94
  }