@versini/auth-provider 2.0.1 → 2.0.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.
- package/dist/index.js +253 -162
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { useCallback as
|
|
1
|
+
import { jsx as z } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback as w, useState as x, useEffect as f, useRef as R, useLayoutEffect as F, createContext as M, useContext as H } from "react";
|
|
3
3
|
/*!
|
|
4
|
-
@versini/auth-provider v2.0.
|
|
4
|
+
@versini/auth-provider v2.0.2
|
|
5
5
|
© 2024 gizmette.com
|
|
6
6
|
*/
|
|
7
7
|
try {
|
|
8
8
|
window.__VERSINI_AUTH_CLIENT__ || (window.__VERSINI_AUTH_CLIENT__ = {
|
|
9
|
-
version: "2.0.
|
|
10
|
-
buildTime: "06/
|
|
9
|
+
version: "2.0.2",
|
|
10
|
+
buildTime: "06/20/2024 03:39 AM EDT",
|
|
11
11
|
homepage: "https://github.com/aversini/auth-client",
|
|
12
12
|
license: "MIT"
|
|
13
13
|
});
|
|
@@ -20,238 +20,329 @@ try {
|
|
|
20
20
|
try {
|
|
21
21
|
window.__VERSINI_AUTH_COMMON__ || (window.__VERSINI_AUTH_COMMON__ = {
|
|
22
22
|
version: "2.0.0",
|
|
23
|
-
buildTime: "06/
|
|
23
|
+
buildTime: "06/20/2024 03:38 AM EDT",
|
|
24
24
|
homepage: "https://github.com/aversini/auth-client",
|
|
25
25
|
license: "MIT"
|
|
26
26
|
});
|
|
27
27
|
} catch {
|
|
28
28
|
}
|
|
29
|
-
const
|
|
29
|
+
const X = {
|
|
30
30
|
ID_TOKEN: "id_token"
|
|
31
|
-
},
|
|
31
|
+
}, G = {
|
|
32
32
|
CLIENT_ID: "X-Auth-ClientId"
|
|
33
33
|
};
|
|
34
|
-
var
|
|
35
|
-
function
|
|
36
|
-
const c =
|
|
37
|
-
|
|
38
|
-
c.current =
|
|
39
|
-
}, [
|
|
40
|
-
const
|
|
41
|
-
if (!(
|
|
34
|
+
var j = typeof window < "u" ? F : f;
|
|
35
|
+
function b(t, e, o, n) {
|
|
36
|
+
const c = R(e);
|
|
37
|
+
j(() => {
|
|
38
|
+
c.current = e;
|
|
39
|
+
}, [e]), f(() => {
|
|
40
|
+
const d = window;
|
|
41
|
+
if (!(d && d.addEventListener))
|
|
42
42
|
return;
|
|
43
43
|
const u = (h) => {
|
|
44
44
|
c.current(h);
|
|
45
45
|
};
|
|
46
|
-
return
|
|
47
|
-
|
|
46
|
+
return d.addEventListener(t, u, n), () => {
|
|
47
|
+
d.removeEventListener(t, u, n);
|
|
48
48
|
};
|
|
49
|
-
}, [
|
|
49
|
+
}, [t, o, n]);
|
|
50
50
|
}
|
|
51
|
-
function
|
|
52
|
-
const
|
|
51
|
+
function A(t) {
|
|
52
|
+
const e = R(() => {
|
|
53
53
|
throw new Error("Cannot call an event handler while rendering.");
|
|
54
54
|
});
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
}, [
|
|
58
|
-
var
|
|
59
|
-
return (
|
|
60
|
-
}, [
|
|
55
|
+
return j(() => {
|
|
56
|
+
e.current = t;
|
|
57
|
+
}, [t]), w((...o) => {
|
|
58
|
+
var n;
|
|
59
|
+
return (n = e.current) == null ? void 0 : n.call(e, ...o);
|
|
60
|
+
}, [e]);
|
|
61
61
|
}
|
|
62
|
-
var
|
|
63
|
-
function
|
|
64
|
-
const { initializeWithValue:
|
|
65
|
-
(
|
|
66
|
-
[
|
|
67
|
-
),
|
|
68
|
-
(
|
|
69
|
-
if (
|
|
70
|
-
return
|
|
71
|
-
if (
|
|
62
|
+
var k = typeof window > "u";
|
|
63
|
+
function S(t, e, o = {}) {
|
|
64
|
+
const { initializeWithValue: n = !0 } = o, c = w(
|
|
65
|
+
(r) => o.serializer ? o.serializer(r) : JSON.stringify(r),
|
|
66
|
+
[o]
|
|
67
|
+
), d = w(
|
|
68
|
+
(r) => {
|
|
69
|
+
if (o.deserializer)
|
|
70
|
+
return o.deserializer(r);
|
|
71
|
+
if (r === "undefined")
|
|
72
72
|
return;
|
|
73
|
-
const a =
|
|
74
|
-
let
|
|
73
|
+
const a = e instanceof Function ? e() : e;
|
|
74
|
+
let g;
|
|
75
75
|
try {
|
|
76
|
-
|
|
77
|
-
} catch (
|
|
78
|
-
return console.error("Error parsing JSON:",
|
|
76
|
+
g = JSON.parse(r);
|
|
77
|
+
} catch (y) {
|
|
78
|
+
return console.error("Error parsing JSON:", y), a;
|
|
79
79
|
}
|
|
80
|
-
return
|
|
80
|
+
return g;
|
|
81
81
|
},
|
|
82
|
-
[
|
|
83
|
-
), u =
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
return
|
|
82
|
+
[o, e]
|
|
83
|
+
), u = w(() => {
|
|
84
|
+
const r = e instanceof Function ? e() : e;
|
|
85
|
+
if (k)
|
|
86
|
+
return r;
|
|
87
87
|
try {
|
|
88
|
-
const a = window.localStorage.getItem(
|
|
89
|
-
return a ?
|
|
88
|
+
const a = window.localStorage.getItem(t);
|
|
89
|
+
return a ? d(a) : r;
|
|
90
90
|
} catch (a) {
|
|
91
|
-
return console.warn(`Error reading localStorage key “${
|
|
91
|
+
return console.warn(`Error reading localStorage key “${t}”:`, a), r;
|
|
92
92
|
}
|
|
93
|
-
}, [
|
|
94
|
-
|
|
95
|
-
`Tried setting localStorage key “${
|
|
93
|
+
}, [e, t, d]), [h, p] = x(() => n ? u() : e instanceof Function ? e() : e), _ = A((r) => {
|
|
94
|
+
k && console.warn(
|
|
95
|
+
`Tried setting localStorage key “${t}” even though environment is not a client`
|
|
96
96
|
);
|
|
97
97
|
try {
|
|
98
|
-
const a =
|
|
99
|
-
window.localStorage.setItem(
|
|
98
|
+
const a = r instanceof Function ? r(u()) : r;
|
|
99
|
+
window.localStorage.setItem(t, c(a)), p(a), window.dispatchEvent(new StorageEvent("local-storage", { key: t }));
|
|
100
100
|
} catch (a) {
|
|
101
|
-
console.warn(`Error setting localStorage key “${
|
|
101
|
+
console.warn(`Error setting localStorage key “${t}”:`, a);
|
|
102
102
|
}
|
|
103
|
-
}), i =
|
|
104
|
-
|
|
105
|
-
`Tried removing localStorage key “${
|
|
103
|
+
}), i = A(() => {
|
|
104
|
+
k && console.warn(
|
|
105
|
+
`Tried removing localStorage key “${t}” even though environment is not a client`
|
|
106
106
|
);
|
|
107
|
-
const
|
|
108
|
-
window.localStorage.removeItem(
|
|
107
|
+
const r = e instanceof Function ? e() : e;
|
|
108
|
+
window.localStorage.removeItem(t), p(r), window.dispatchEvent(new StorageEvent("local-storage", { key: t }));
|
|
109
109
|
});
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}, [
|
|
113
|
-
const
|
|
114
|
-
(
|
|
115
|
-
|
|
110
|
+
f(() => {
|
|
111
|
+
p(u());
|
|
112
|
+
}, [t]);
|
|
113
|
+
const v = w(
|
|
114
|
+
(r) => {
|
|
115
|
+
r.key && r.key !== t || p(u());
|
|
116
116
|
},
|
|
117
|
-
[
|
|
117
|
+
[t, u]
|
|
118
118
|
);
|
|
119
|
-
return
|
|
119
|
+
return b("storage", v), b("local-storage", v), [h, _, i];
|
|
120
|
+
}
|
|
121
|
+
new TextEncoder();
|
|
122
|
+
const L = new TextDecoder(), K = (t) => {
|
|
123
|
+
const e = atob(t), o = new Uint8Array(e.length);
|
|
124
|
+
for (let n = 0; n < e.length; n++)
|
|
125
|
+
o[n] = e.charCodeAt(n);
|
|
126
|
+
return o;
|
|
127
|
+
}, B = (t) => {
|
|
128
|
+
let e = t;
|
|
129
|
+
e instanceof Uint8Array && (e = L.decode(e)), e = e.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, "");
|
|
130
|
+
try {
|
|
131
|
+
return K(e);
|
|
132
|
+
} catch {
|
|
133
|
+
throw new TypeError("The input to be decoded is not correctly encoded.");
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
class Y extends Error {
|
|
137
|
+
static get code() {
|
|
138
|
+
return "ERR_JOSE_GENERIC";
|
|
139
|
+
}
|
|
140
|
+
constructor(e) {
|
|
141
|
+
var o;
|
|
142
|
+
super(e), this.code = "ERR_JOSE_GENERIC", this.name = this.constructor.name, (o = Error.captureStackTrace) == null || o.call(Error, this, this.constructor);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
class T extends Y {
|
|
146
|
+
constructor() {
|
|
147
|
+
super(...arguments), this.code = "ERR_JWT_INVALID";
|
|
148
|
+
}
|
|
149
|
+
static get code() {
|
|
150
|
+
return "ERR_JWT_INVALID";
|
|
151
|
+
}
|
|
120
152
|
}
|
|
121
|
-
|
|
153
|
+
function q(t) {
|
|
154
|
+
return typeof t == "object" && t !== null;
|
|
155
|
+
}
|
|
156
|
+
function Q(t) {
|
|
157
|
+
if (!q(t) || Object.prototype.toString.call(t) !== "[object Object]")
|
|
158
|
+
return !1;
|
|
159
|
+
if (Object.getPrototypeOf(t) === null)
|
|
160
|
+
return !0;
|
|
161
|
+
let e = t;
|
|
162
|
+
for (; Object.getPrototypeOf(e) !== null; )
|
|
163
|
+
e = Object.getPrototypeOf(e);
|
|
164
|
+
return Object.getPrototypeOf(t) === e;
|
|
165
|
+
}
|
|
166
|
+
const Z = B;
|
|
167
|
+
function N(t) {
|
|
168
|
+
if (typeof t != "string")
|
|
169
|
+
throw new T("JWTs must use Compact JWS serialization, JWT must be a string");
|
|
170
|
+
const { 1: e, length: o } = t.split(".");
|
|
171
|
+
if (o === 5)
|
|
172
|
+
throw new T("Only JWTs using Compact JWS serialization can be decoded");
|
|
173
|
+
if (o !== 3)
|
|
174
|
+
throw new T("Invalid JWT");
|
|
175
|
+
if (!e)
|
|
176
|
+
throw new T("JWTs must contain a payload");
|
|
177
|
+
let n;
|
|
178
|
+
try {
|
|
179
|
+
n = Z(e);
|
|
180
|
+
} catch {
|
|
181
|
+
throw new T("Failed to base64url decode the payload");
|
|
182
|
+
}
|
|
183
|
+
let c;
|
|
184
|
+
try {
|
|
185
|
+
c = JSON.parse(L.decode(n));
|
|
186
|
+
} catch {
|
|
187
|
+
throw new T("Failed to parse the decoded payload as JSON");
|
|
188
|
+
}
|
|
189
|
+
if (!Q(c))
|
|
190
|
+
throw new T("Invalid JWT Claims Set");
|
|
191
|
+
return c;
|
|
192
|
+
}
|
|
193
|
+
const C = "Oops! It looks like your session has expired. For your security, please log in again to continue.", ee = "You forgot to wrap your component in <AuthProvider>.", J = {
|
|
122
194
|
dev: "https://auth.gizmette.local.com:3003",
|
|
123
195
|
prod: "https://mylogin.gizmette.com"
|
|
124
196
|
};
|
|
125
197
|
var s = [];
|
|
126
|
-
for (var
|
|
127
|
-
s.push((
|
|
128
|
-
function
|
|
129
|
-
return (s[e
|
|
198
|
+
for (var O = 0; O < 256; ++O)
|
|
199
|
+
s.push((O + 256).toString(16).slice(1));
|
|
200
|
+
function te(t, e = 0) {
|
|
201
|
+
return (s[t[e + 0]] + s[t[e + 1]] + s[t[e + 2]] + s[t[e + 3]] + "-" + s[t[e + 4]] + s[t[e + 5]] + "-" + s[t[e + 6]] + s[t[e + 7]] + "-" + s[t[e + 8]] + s[t[e + 9]] + "-" + s[t[e + 10]] + s[t[e + 11]] + s[t[e + 12]] + s[t[e + 13]] + s[t[e + 14]] + s[t[e + 15]]).toLowerCase();
|
|
130
202
|
}
|
|
131
|
-
var m,
|
|
132
|
-
function
|
|
203
|
+
var m, oe = new Uint8Array(16);
|
|
204
|
+
function ne() {
|
|
133
205
|
if (!m && (m = typeof crypto < "u" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto), !m))
|
|
134
206
|
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
|
|
135
|
-
return m(
|
|
207
|
+
return m(oe);
|
|
136
208
|
}
|
|
137
|
-
var
|
|
138
|
-
const
|
|
139
|
-
randomUUID:
|
|
209
|
+
var re = typeof crypto < "u" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
|
210
|
+
const D = {
|
|
211
|
+
randomUUID: re
|
|
140
212
|
};
|
|
141
|
-
function
|
|
142
|
-
if (
|
|
143
|
-
return
|
|
144
|
-
|
|
145
|
-
var
|
|
146
|
-
return
|
|
213
|
+
function se(t, e, o) {
|
|
214
|
+
if (D.randomUUID && !e && !t)
|
|
215
|
+
return D.randomUUID();
|
|
216
|
+
t = t || {};
|
|
217
|
+
var n = t.random || (t.rng || ne)();
|
|
218
|
+
return n[6] = n[6] & 15 | 64, n[8] = n[8] & 63 | 128, te(n);
|
|
147
219
|
}
|
|
148
|
-
const
|
|
220
|
+
const ce = process.env.NODE_ENV === "production", ae = !ce, ie = async ({ params: t = {} }) => {
|
|
149
221
|
try {
|
|
150
|
-
const
|
|
151
|
-
|
|
222
|
+
const e = se(), o = await fetch(
|
|
223
|
+
ae ? `${J.dev}/authenticate` : `${J.prod}/authenticate`,
|
|
152
224
|
{
|
|
153
225
|
credentials: "include",
|
|
154
226
|
method: "POST",
|
|
155
227
|
headers: {
|
|
156
228
|
"Content-Type": "application/json",
|
|
157
|
-
[
|
|
229
|
+
[G.CLIENT_ID]: `${t.clientId}`
|
|
158
230
|
},
|
|
159
|
-
body: JSON.stringify({ ...
|
|
231
|
+
body: JSON.stringify({ ...t, nonce: e })
|
|
160
232
|
}
|
|
161
233
|
);
|
|
162
|
-
if (
|
|
163
|
-
return { status:
|
|
164
|
-
const { data:
|
|
165
|
-
return
|
|
166
|
-
status:
|
|
167
|
-
data:
|
|
234
|
+
if (o.status !== 200)
|
|
235
|
+
return { status: o.status, data: [] };
|
|
236
|
+
const { data: n, errors: c } = await o.json();
|
|
237
|
+
return n.nonce !== e ? { status: 500, data: [] } : {
|
|
238
|
+
status: o.status,
|
|
239
|
+
data: n,
|
|
168
240
|
errors: c
|
|
169
241
|
};
|
|
170
|
-
} catch (
|
|
171
|
-
return console.error(
|
|
242
|
+
} catch (e) {
|
|
243
|
+
return console.error(e), { status: 500, data: [] };
|
|
172
244
|
}
|
|
173
245
|
};
|
|
174
|
-
function
|
|
175
|
-
const
|
|
176
|
-
return
|
|
177
|
-
|
|
178
|
-
}),
|
|
246
|
+
function ue(t) {
|
|
247
|
+
const e = R();
|
|
248
|
+
return f(() => {
|
|
249
|
+
e.current = t;
|
|
250
|
+
}), e.current;
|
|
179
251
|
}
|
|
180
|
-
const
|
|
181
|
-
throw new Error(
|
|
182
|
-
},
|
|
252
|
+
const U = () => {
|
|
253
|
+
throw new Error(ee);
|
|
254
|
+
}, P = M({
|
|
183
255
|
isAuthenticated: !1,
|
|
184
|
-
login:
|
|
185
|
-
logout:
|
|
256
|
+
login: U,
|
|
257
|
+
logout: U,
|
|
186
258
|
accessToken: void 0,
|
|
187
259
|
refreshToken: void 0,
|
|
188
260
|
idToken: void 0,
|
|
189
261
|
logoutReason: ""
|
|
190
|
-
}),
|
|
191
|
-
children:
|
|
192
|
-
sessionExpiration:
|
|
193
|
-
clientId:
|
|
194
|
-
accessType:
|
|
262
|
+
}), he = ({
|
|
263
|
+
children: t,
|
|
264
|
+
sessionExpiration: e,
|
|
265
|
+
clientId: o,
|
|
266
|
+
accessType: n
|
|
195
267
|
}) => {
|
|
196
|
-
const [c,
|
|
197
|
-
`@@auth@@::${
|
|
268
|
+
const [c, d, u] = S(
|
|
269
|
+
`@@auth@@::${o}::@@access@@`,
|
|
198
270
|
""
|
|
199
|
-
), [h,
|
|
200
|
-
`@@auth@@::${
|
|
271
|
+
), [h, p, _] = S(
|
|
272
|
+
`@@auth@@::${o}::@@refresh@@`,
|
|
201
273
|
""
|
|
202
|
-
), [i,
|
|
203
|
-
`@@auth@@::${
|
|
274
|
+
), [i, v, r] = S(
|
|
275
|
+
`@@auth@@::${o}::@@user@@`,
|
|
204
276
|
""
|
|
205
|
-
), [a,
|
|
277
|
+
), [a, g] = x({
|
|
206
278
|
isAuthenticated: !!i,
|
|
207
279
|
accessToken: c,
|
|
208
280
|
refreshToken: h,
|
|
209
281
|
idToken: i,
|
|
210
282
|
logoutReason: "",
|
|
211
283
|
userId: ""
|
|
212
|
-
}),
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
284
|
+
}), y = ue(i) || "";
|
|
285
|
+
f(() => {
|
|
286
|
+
if (y !== i && i !== "")
|
|
287
|
+
try {
|
|
288
|
+
const { _id: E } = N(i);
|
|
289
|
+
g({
|
|
290
|
+
isAuthenticated: !0,
|
|
291
|
+
accessToken: c,
|
|
292
|
+
refreshToken: h,
|
|
293
|
+
idToken: i,
|
|
294
|
+
logoutReason: "",
|
|
295
|
+
userId: E || ""
|
|
296
|
+
});
|
|
297
|
+
} catch {
|
|
298
|
+
g({
|
|
299
|
+
isAuthenticated: !1,
|
|
300
|
+
accessToken: "",
|
|
301
|
+
refreshToken: "",
|
|
302
|
+
idToken: "",
|
|
303
|
+
logoutReason: C,
|
|
304
|
+
userId: ""
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
else
|
|
308
|
+
y !== i && i === "" && g({
|
|
309
|
+
isAuthenticated: !1,
|
|
310
|
+
accessToken: "",
|
|
311
|
+
refreshToken: "",
|
|
312
|
+
idToken: "",
|
|
313
|
+
logoutReason: C,
|
|
314
|
+
userId: ""
|
|
315
|
+
});
|
|
316
|
+
}, [c, h, i, y]);
|
|
317
|
+
const $ = async (E, W) => {
|
|
318
|
+
const l = await ie({
|
|
233
319
|
params: {
|
|
234
|
-
type:
|
|
235
|
-
username:
|
|
236
|
-
password:
|
|
237
|
-
sessionExpiration:
|
|
238
|
-
clientId:
|
|
320
|
+
type: n || X.ID_TOKEN,
|
|
321
|
+
username: E,
|
|
322
|
+
password: W,
|
|
323
|
+
sessionExpiration: e,
|
|
324
|
+
clientId: o
|
|
239
325
|
}
|
|
240
326
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
idToken
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
327
|
+
try {
|
|
328
|
+
const { _id: I } = N(l.data.idToken);
|
|
329
|
+
return I ? (v(l.data.idToken), l.data.accessToken && d(l.data.accessToken), l.data.refreshToken && p(l.data.refreshToken), g({
|
|
330
|
+
isAuthenticated: !0,
|
|
331
|
+
idToken: l.data.idToken,
|
|
332
|
+
accessToken: l.data.accessToken,
|
|
333
|
+
refreshToken: l.data.refreshToken,
|
|
334
|
+
userId: I,
|
|
335
|
+
logoutReason: ""
|
|
336
|
+
}), !0) : !1;
|
|
337
|
+
} catch {
|
|
338
|
+
return !1;
|
|
339
|
+
}
|
|
340
|
+
}, V = () => {
|
|
341
|
+
u(), _(), r();
|
|
251
342
|
};
|
|
252
|
-
return /* @__PURE__ */ P
|
|
253
|
-
},
|
|
343
|
+
return /* @__PURE__ */ z(P.Provider, { value: { ...a, login: $, logout: V }, children: t });
|
|
344
|
+
}, ge = (t = P) => H(t);
|
|
254
345
|
export {
|
|
255
|
-
|
|
256
|
-
|
|
346
|
+
he as AuthProvider,
|
|
347
|
+
ge as useAuth
|
|
257
348
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versini/auth-provider",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Arno Versini",
|
|
6
6
|
"publishConfig": {
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@versini/auth-common": "2.0.0",
|
|
48
48
|
"@versini/ui-hooks": "3.0.0",
|
|
49
|
+
"jose": "5.4.1",
|
|
49
50
|
"uuid": "10.0.0"
|
|
50
51
|
},
|
|
51
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "e60af859117bfbd5af8dd27504cd1ca214c28194"
|
|
52
53
|
}
|