react-visual-feedback 1.4.9 → 2.2.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 +89 -3
- package/dist/index.esm.js +2 -2
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/config.js +664 -0
- package/dist/integrations/config.js.map +1 -0
- package/dist/integrations/index.js +4 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/server/index.js +2907 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/jira.js +1565 -0
- package/dist/server/jira.js.map +1 -0
- package/dist/server/sheets.js +1448 -0
- package/dist/server/sheets.js.map +1 -0
- package/package.json +30 -3
|
@@ -0,0 +1,1448 @@
|
|
|
1
|
+
function _arrayLikeToArray(r, a) {
|
|
2
|
+
(null == a || a > r.length) && (a = r.length);
|
|
3
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
4
|
+
return n;
|
|
5
|
+
}
|
|
6
|
+
function _arrayWithHoles(r) {
|
|
7
|
+
if (Array.isArray(r)) return r;
|
|
8
|
+
}
|
|
9
|
+
function _assertThisInitialized(e) {
|
|
10
|
+
if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
|
11
|
+
return e;
|
|
12
|
+
}
|
|
13
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) {
|
|
14
|
+
try {
|
|
15
|
+
var i = n[a](c),
|
|
16
|
+
u = i.value;
|
|
17
|
+
} catch (n) {
|
|
18
|
+
return void e(n);
|
|
19
|
+
}
|
|
20
|
+
i.done ? t(u) : Promise.resolve(u).then(r, o);
|
|
21
|
+
}
|
|
22
|
+
function _asyncToGenerator(n) {
|
|
23
|
+
return function () {
|
|
24
|
+
var t = this,
|
|
25
|
+
e = arguments;
|
|
26
|
+
return new Promise(function (r, o) {
|
|
27
|
+
var a = n.apply(t, e);
|
|
28
|
+
function _next(n) {
|
|
29
|
+
asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
|
|
30
|
+
}
|
|
31
|
+
function _throw(n) {
|
|
32
|
+
asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
|
|
33
|
+
}
|
|
34
|
+
_next(void 0);
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function _callSuper(t, o, e) {
|
|
39
|
+
return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e));
|
|
40
|
+
}
|
|
41
|
+
function _classCallCheck(a, n) {
|
|
42
|
+
if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
|
|
43
|
+
}
|
|
44
|
+
function _defineProperties(e, r) {
|
|
45
|
+
for (var t = 0; t < r.length; t++) {
|
|
46
|
+
var o = r[t];
|
|
47
|
+
o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, _toPropertyKey(o.key), o);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function _createClass(e, r, t) {
|
|
51
|
+
return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
|
|
52
|
+
writable: false
|
|
53
|
+
}), e;
|
|
54
|
+
}
|
|
55
|
+
function _defineProperty(e, r, t) {
|
|
56
|
+
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
57
|
+
value: t,
|
|
58
|
+
enumerable: true,
|
|
59
|
+
configurable: true,
|
|
60
|
+
writable: true
|
|
61
|
+
}) : e[r] = t, e;
|
|
62
|
+
}
|
|
63
|
+
function _getPrototypeOf(t) {
|
|
64
|
+
return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) {
|
|
65
|
+
return t.__proto__ || Object.getPrototypeOf(t);
|
|
66
|
+
}, _getPrototypeOf(t);
|
|
67
|
+
}
|
|
68
|
+
function _inherits(t, e) {
|
|
69
|
+
if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function");
|
|
70
|
+
t.prototype = Object.create(e && e.prototype, {
|
|
71
|
+
constructor: {
|
|
72
|
+
value: t,
|
|
73
|
+
writable: true,
|
|
74
|
+
configurable: true
|
|
75
|
+
}
|
|
76
|
+
}), Object.defineProperty(t, "prototype", {
|
|
77
|
+
writable: false
|
|
78
|
+
}), e && _setPrototypeOf(t, e);
|
|
79
|
+
}
|
|
80
|
+
function _isNativeReflectConstruct() {
|
|
81
|
+
try {
|
|
82
|
+
var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
|
|
83
|
+
} catch (t) {}
|
|
84
|
+
return (_isNativeReflectConstruct = function () {
|
|
85
|
+
return !!t;
|
|
86
|
+
})();
|
|
87
|
+
}
|
|
88
|
+
function _iterableToArrayLimit(r, l) {
|
|
89
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
90
|
+
if (null != t) {
|
|
91
|
+
var e,
|
|
92
|
+
n,
|
|
93
|
+
i,
|
|
94
|
+
u,
|
|
95
|
+
a = [],
|
|
96
|
+
f = true,
|
|
97
|
+
o = false;
|
|
98
|
+
try {
|
|
99
|
+
if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
|
100
|
+
} catch (r) {
|
|
101
|
+
o = true, n = r;
|
|
102
|
+
} finally {
|
|
103
|
+
try {
|
|
104
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
105
|
+
} finally {
|
|
106
|
+
if (o) throw n;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return a;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function _nonIterableRest() {
|
|
113
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
114
|
+
}
|
|
115
|
+
function ownKeys(e, r) {
|
|
116
|
+
var t = Object.keys(e);
|
|
117
|
+
if (Object.getOwnPropertySymbols) {
|
|
118
|
+
var o = Object.getOwnPropertySymbols(e);
|
|
119
|
+
r && (o = o.filter(function (r) {
|
|
120
|
+
return Object.getOwnPropertyDescriptor(e, r).enumerable;
|
|
121
|
+
})), t.push.apply(t, o);
|
|
122
|
+
}
|
|
123
|
+
return t;
|
|
124
|
+
}
|
|
125
|
+
function _objectSpread2(e) {
|
|
126
|
+
for (var r = 1; r < arguments.length; r++) {
|
|
127
|
+
var t = null != arguments[r] ? arguments[r] : {};
|
|
128
|
+
r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
|
|
129
|
+
_defineProperty(e, r, t[r]);
|
|
130
|
+
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
|
|
131
|
+
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return e;
|
|
135
|
+
}
|
|
136
|
+
function _possibleConstructorReturn(t, e) {
|
|
137
|
+
if (e && ("object" == typeof e || "function" == typeof e)) return e;
|
|
138
|
+
if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined");
|
|
139
|
+
return _assertThisInitialized(t);
|
|
140
|
+
}
|
|
141
|
+
function _regenerator() {
|
|
142
|
+
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
|
|
143
|
+
var e,
|
|
144
|
+
t,
|
|
145
|
+
r = "function" == typeof Symbol ? Symbol : {},
|
|
146
|
+
n = r.iterator || "@@iterator",
|
|
147
|
+
o = r.toStringTag || "@@toStringTag";
|
|
148
|
+
function i(r, n, o, i) {
|
|
149
|
+
var c = n && n.prototype instanceof Generator ? n : Generator,
|
|
150
|
+
u = Object.create(c.prototype);
|
|
151
|
+
return _regeneratorDefine(u, "_invoke", function (r, n, o) {
|
|
152
|
+
var i,
|
|
153
|
+
c,
|
|
154
|
+
u,
|
|
155
|
+
f = 0,
|
|
156
|
+
p = o || [],
|
|
157
|
+
y = false,
|
|
158
|
+
G = {
|
|
159
|
+
p: 0,
|
|
160
|
+
n: 0,
|
|
161
|
+
v: e,
|
|
162
|
+
a: d,
|
|
163
|
+
f: d.bind(e, 4),
|
|
164
|
+
d: function (t, r) {
|
|
165
|
+
return i = t, c = 0, u = e, G.n = r, a;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
function d(r, n) {
|
|
169
|
+
for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) {
|
|
170
|
+
var o,
|
|
171
|
+
i = p[t],
|
|
172
|
+
d = G.p,
|
|
173
|
+
l = i[2];
|
|
174
|
+
r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0));
|
|
175
|
+
}
|
|
176
|
+
if (o || r > 1) return a;
|
|
177
|
+
throw y = true, n;
|
|
178
|
+
}
|
|
179
|
+
return function (o, p, l) {
|
|
180
|
+
if (f > 1) throw TypeError("Generator is already running");
|
|
181
|
+
for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) {
|
|
182
|
+
i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u);
|
|
183
|
+
try {
|
|
184
|
+
if (f = 2, i) {
|
|
185
|
+
if (c || (o = "next"), t = i[o]) {
|
|
186
|
+
if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object");
|
|
187
|
+
if (!t.done) return t;
|
|
188
|
+
u = t.value, c < 2 && (c = 0);
|
|
189
|
+
} else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1);
|
|
190
|
+
i = e;
|
|
191
|
+
} else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break;
|
|
192
|
+
} catch (t) {
|
|
193
|
+
i = e, c = 1, u = t;
|
|
194
|
+
} finally {
|
|
195
|
+
f = 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
value: t,
|
|
200
|
+
done: y
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
}(r, o, i), true), u;
|
|
204
|
+
}
|
|
205
|
+
var a = {};
|
|
206
|
+
function Generator() {}
|
|
207
|
+
function GeneratorFunction() {}
|
|
208
|
+
function GeneratorFunctionPrototype() {}
|
|
209
|
+
t = Object.getPrototypeOf;
|
|
210
|
+
var c = [][n] ? t(t([][n]())) : (_regeneratorDefine(t = {}, n, function () {
|
|
211
|
+
return this;
|
|
212
|
+
}), t),
|
|
213
|
+
u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c);
|
|
214
|
+
function f(e) {
|
|
215
|
+
return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e;
|
|
216
|
+
}
|
|
217
|
+
return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine(u), _regeneratorDefine(u, o, "Generator"), _regeneratorDefine(u, n, function () {
|
|
218
|
+
return this;
|
|
219
|
+
}), _regeneratorDefine(u, "toString", function () {
|
|
220
|
+
return "[object Generator]";
|
|
221
|
+
}), (_regenerator = function () {
|
|
222
|
+
return {
|
|
223
|
+
w: i,
|
|
224
|
+
m: f
|
|
225
|
+
};
|
|
226
|
+
})();
|
|
227
|
+
}
|
|
228
|
+
function _regeneratorDefine(e, r, n, t) {
|
|
229
|
+
var i = Object.defineProperty;
|
|
230
|
+
try {
|
|
231
|
+
i({}, "", {});
|
|
232
|
+
} catch (e) {
|
|
233
|
+
i = 0;
|
|
234
|
+
}
|
|
235
|
+
_regeneratorDefine = function (e, r, n, t) {
|
|
236
|
+
function o(r, n) {
|
|
237
|
+
_regeneratorDefine(e, r, function (e) {
|
|
238
|
+
return this._invoke(r, n, e);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
r ? i ? i(e, r, {
|
|
242
|
+
value: n,
|
|
243
|
+
enumerable: !t,
|
|
244
|
+
configurable: !t,
|
|
245
|
+
writable: !t
|
|
246
|
+
}) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2));
|
|
247
|
+
}, _regeneratorDefine(e, r, n, t);
|
|
248
|
+
}
|
|
249
|
+
function _setPrototypeOf(t, e) {
|
|
250
|
+
return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) {
|
|
251
|
+
return t.__proto__ = e, t;
|
|
252
|
+
}, _setPrototypeOf(t, e);
|
|
253
|
+
}
|
|
254
|
+
function _slicedToArray(r, e) {
|
|
255
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
256
|
+
}
|
|
257
|
+
function _toPrimitive(t, r) {
|
|
258
|
+
if ("object" != typeof t || !t) return t;
|
|
259
|
+
var e = t[Symbol.toPrimitive];
|
|
260
|
+
if (void 0 !== e) {
|
|
261
|
+
var i = e.call(t, r);
|
|
262
|
+
if ("object" != typeof i) return i;
|
|
263
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
264
|
+
}
|
|
265
|
+
return ("string" === r ? String : Number)(t);
|
|
266
|
+
}
|
|
267
|
+
function _toPropertyKey(t) {
|
|
268
|
+
var i = _toPrimitive(t, "string");
|
|
269
|
+
return "symbol" == typeof i ? i : i + "";
|
|
270
|
+
}
|
|
271
|
+
function _typeof(o) {
|
|
272
|
+
"@babel/helpers - typeof";
|
|
273
|
+
|
|
274
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
|
|
275
|
+
return typeof o;
|
|
276
|
+
} : function (o) {
|
|
277
|
+
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
278
|
+
}, _typeof(o);
|
|
279
|
+
}
|
|
280
|
+
function _unsupportedIterableToArray(r, a) {
|
|
281
|
+
if (r) {
|
|
282
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
283
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
284
|
+
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ============================================
|
|
289
|
+
// GOOGLE SHEETS COLUMN MAPPING
|
|
290
|
+
// ============================================
|
|
291
|
+
|
|
292
|
+
var DEFAULT_SHEET_COLUMNS = {
|
|
293
|
+
timestamp: {
|
|
294
|
+
key: 'timestamp',
|
|
295
|
+
header: 'Timestamp',
|
|
296
|
+
field: 'timestamp',
|
|
297
|
+
transform: function transform(value) {
|
|
298
|
+
return value || new Date().toISOString();
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
id: {
|
|
302
|
+
key: 'id',
|
|
303
|
+
header: 'Feedback ID',
|
|
304
|
+
field: 'id'
|
|
305
|
+
},
|
|
306
|
+
feedback: {
|
|
307
|
+
key: 'feedback',
|
|
308
|
+
header: 'Feedback',
|
|
309
|
+
field: 'feedback'
|
|
310
|
+
},
|
|
311
|
+
type: {
|
|
312
|
+
key: 'type',
|
|
313
|
+
header: 'Type',
|
|
314
|
+
field: 'type',
|
|
315
|
+
transform: function transform(value) {
|
|
316
|
+
return value || 'bug';
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
status: {
|
|
320
|
+
key: 'status',
|
|
321
|
+
header: 'Status',
|
|
322
|
+
field: 'status',
|
|
323
|
+
transform: function transform(value) {
|
|
324
|
+
return value || 'new';
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
userName: {
|
|
328
|
+
key: 'userName',
|
|
329
|
+
header: 'User Name',
|
|
330
|
+
field: 'userName',
|
|
331
|
+
transform: function transform(value) {
|
|
332
|
+
return value || 'Anonymous';
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
userEmail: {
|
|
336
|
+
key: 'userEmail',
|
|
337
|
+
header: 'User Email',
|
|
338
|
+
field: 'userEmail',
|
|
339
|
+
transform: function transform(value) {
|
|
340
|
+
return value || '';
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
url: {
|
|
344
|
+
key: 'url',
|
|
345
|
+
header: 'Page URL',
|
|
346
|
+
field: 'url'
|
|
347
|
+
},
|
|
348
|
+
viewport: {
|
|
349
|
+
key: 'viewport',
|
|
350
|
+
header: 'Viewport',
|
|
351
|
+
field: 'viewport',
|
|
352
|
+
transform: function transform(value) {
|
|
353
|
+
return value ? "".concat(value.width, "x").concat(value.height) : '';
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
userAgent: {
|
|
357
|
+
key: 'userAgent',
|
|
358
|
+
header: 'Browser',
|
|
359
|
+
field: 'userAgent',
|
|
360
|
+
transform: function transform(value) {
|
|
361
|
+
if (!value) return '';
|
|
362
|
+
// Simplify user agent
|
|
363
|
+
if (value.includes('Chrome')) return 'Chrome';
|
|
364
|
+
if (value.includes('Firefox')) return 'Firefox';
|
|
365
|
+
if (value.includes('Safari')) return 'Safari';
|
|
366
|
+
if (value.includes('Edge')) return 'Edge';
|
|
367
|
+
return value.substring(0, 50);
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
screenshot: {
|
|
371
|
+
key: 'screenshot',
|
|
372
|
+
header: 'Screenshot',
|
|
373
|
+
field: 'screenshot',
|
|
374
|
+
transform: function transform(value) {
|
|
375
|
+
return value ? 'Yes' : 'No';
|
|
376
|
+
} // Don't store base64 in sheets
|
|
377
|
+
},
|
|
378
|
+
video: {
|
|
379
|
+
key: 'video',
|
|
380
|
+
header: 'Video',
|
|
381
|
+
field: 'video',
|
|
382
|
+
transform: function transform(value) {
|
|
383
|
+
return value ? 'Yes' : 'No';
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
elementSelector: {
|
|
387
|
+
key: 'elementSelector',
|
|
388
|
+
header: 'Element',
|
|
389
|
+
field: 'elementInfo',
|
|
390
|
+
transform: function transform(value) {
|
|
391
|
+
return (value === null || value === void 0 ? void 0 : value.selector) || '';
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
componentName: {
|
|
395
|
+
key: 'componentName',
|
|
396
|
+
header: 'Component',
|
|
397
|
+
field: 'elementInfo',
|
|
398
|
+
transform: function transform(value) {
|
|
399
|
+
var _value$componentStack;
|
|
400
|
+
return (value === null || value === void 0 || (_value$componentStack = value.componentStack) === null || _value$componentStack === void 0 ? void 0 : _value$componentStack[0]) || '';
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
sourceFile: {
|
|
404
|
+
key: 'sourceFile',
|
|
405
|
+
header: 'Source File',
|
|
406
|
+
field: 'elementInfo',
|
|
407
|
+
transform: function transform(value) {
|
|
408
|
+
return (value === null || value === void 0 ? void 0 : value.sourceFile) || '';
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
jiraKey: {
|
|
412
|
+
key: 'jiraKey',
|
|
413
|
+
header: 'Jira Issue',
|
|
414
|
+
field: 'jiraKey',
|
|
415
|
+
transform: function transform(value) {
|
|
416
|
+
return value || '';
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// Default columns to include (order matters)
|
|
422
|
+
var DEFAULT_SHEET_COLUMN_ORDER = ['timestamp', 'id', 'feedback', 'type', 'status', 'userName', 'userEmail', 'url', 'viewport', 'screenshot', 'video', 'jiraKey'];
|
|
423
|
+
|
|
424
|
+
// ============================================
|
|
425
|
+
// HELPER FUNCTIONS
|
|
426
|
+
// ============================================
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Merge user config with defaults
|
|
430
|
+
*/
|
|
431
|
+
function mergeSheetColumns() {
|
|
432
|
+
var userColumns = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
433
|
+
var columnOrder = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
434
|
+
var merged = _objectSpread2({}, DEFAULT_SHEET_COLUMNS);
|
|
435
|
+
|
|
436
|
+
// Apply user overrides
|
|
437
|
+
Object.entries(userColumns).forEach(function (_ref) {
|
|
438
|
+
var _ref2 = _slicedToArray(_ref, 2),
|
|
439
|
+
key = _ref2[0],
|
|
440
|
+
value = _ref2[1];
|
|
441
|
+
if (value === null || value === false) {
|
|
442
|
+
// Remove column
|
|
443
|
+
delete merged[key];
|
|
444
|
+
} else if (_typeof(value) === 'object') {
|
|
445
|
+
// Merge with default
|
|
446
|
+
merged[key] = _objectSpread2(_objectSpread2({}, merged[key]), value);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Determine column order
|
|
451
|
+
var order = columnOrder || Object.keys(merged).filter(function (k) {
|
|
452
|
+
return DEFAULT_SHEET_COLUMN_ORDER.includes(k) || userColumns[k];
|
|
453
|
+
});
|
|
454
|
+
return {
|
|
455
|
+
columns: merged,
|
|
456
|
+
order: order
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Transform feedback data to sheet row
|
|
462
|
+
*/
|
|
463
|
+
function feedbackToSheetRow(feedbackData) {
|
|
464
|
+
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
465
|
+
var _mergeSheetColumns = mergeSheetColumns(config.columns, config.columnOrder),
|
|
466
|
+
columns = _mergeSheetColumns.columns,
|
|
467
|
+
order = _mergeSheetColumns.order;
|
|
468
|
+
var row = order.map(function (key) {
|
|
469
|
+
var col = columns[key];
|
|
470
|
+
if (!col) return '';
|
|
471
|
+
var rawValue = feedbackData[col.field];
|
|
472
|
+
if (col.transform) {
|
|
473
|
+
return col.transform(rawValue) || '';
|
|
474
|
+
}
|
|
475
|
+
return rawValue || '';
|
|
476
|
+
});
|
|
477
|
+
return row;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Get sheet headers
|
|
482
|
+
*/
|
|
483
|
+
function getSheetHeaders() {
|
|
484
|
+
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
485
|
+
var _mergeSheetColumns2 = mergeSheetColumns(config.columns, config.columnOrder),
|
|
486
|
+
columns = _mergeSheetColumns2.columns,
|
|
487
|
+
order = _mergeSheetColumns2.order;
|
|
488
|
+
return order.map(function (key) {
|
|
489
|
+
var _columns$key;
|
|
490
|
+
return ((_columns$key = columns[key]) === null || _columns$key === void 0 ? void 0 : _columns$key.header) || key;
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ============================================
|
|
495
|
+
// GOOGLE SHEETS CLIENT (Service Account)
|
|
496
|
+
// ============================================
|
|
497
|
+
var SheetsClient = /*#__PURE__*/function () {
|
|
498
|
+
function SheetsClient(config) {
|
|
499
|
+
_classCallCheck(this, SheetsClient);
|
|
500
|
+
this.spreadsheetId = config.spreadsheetId || process.env.GOOGLE_SPREADSHEET_ID;
|
|
501
|
+
this.sheetName = config.sheetName || 'Feedback';
|
|
502
|
+
|
|
503
|
+
// Service Account credentials
|
|
504
|
+
var credentials = config.credentials || process.env.GOOGLE_SERVICE_ACCOUNT;
|
|
505
|
+
if (!credentials) {
|
|
506
|
+
throw new Error('Google credentials missing. Set GOOGLE_SERVICE_ACCOUNT environment variable.');
|
|
507
|
+
}
|
|
508
|
+
this.credentials = typeof credentials === 'string' ? JSON.parse(credentials) : credentials;
|
|
509
|
+
if (!this.spreadsheetId) {
|
|
510
|
+
throw new Error('Spreadsheet ID missing. Set GOOGLE_SPREADSHEET_ID environment variable.');
|
|
511
|
+
}
|
|
512
|
+
this.accessToken = null;
|
|
513
|
+
this.tokenExpiry = null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Get access token using Service Account JWT
|
|
518
|
+
*/
|
|
519
|
+
return _createClass(SheetsClient, [{
|
|
520
|
+
key: "getAccessToken",
|
|
521
|
+
value: (function () {
|
|
522
|
+
var _getAccessToken = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
523
|
+
var _this$credentials, client_email, private_key, header, now, payload, jwt, response, error, data;
|
|
524
|
+
return _regenerator().w(function (_context) {
|
|
525
|
+
while (1) switch (_context.n) {
|
|
526
|
+
case 0:
|
|
527
|
+
if (!(this.accessToken && this.tokenExpiry && Date.now() < this.tokenExpiry)) {
|
|
528
|
+
_context.n = 1;
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
return _context.a(2, this.accessToken);
|
|
532
|
+
case 1:
|
|
533
|
+
_this$credentials = this.credentials, client_email = _this$credentials.client_email, private_key = _this$credentials.private_key; // Create JWT
|
|
534
|
+
header = {
|
|
535
|
+
alg: 'RS256',
|
|
536
|
+
typ: 'JWT'
|
|
537
|
+
};
|
|
538
|
+
now = Math.floor(Date.now() / 1000);
|
|
539
|
+
payload = {
|
|
540
|
+
iss: client_email,
|
|
541
|
+
scope: 'https://www.googleapis.com/auth/spreadsheets',
|
|
542
|
+
aud: 'https://oauth2.googleapis.com/token',
|
|
543
|
+
exp: now + 3600,
|
|
544
|
+
iat: now
|
|
545
|
+
};
|
|
546
|
+
_context.n = 2;
|
|
547
|
+
return this.createJWT(header, payload, private_key);
|
|
548
|
+
case 2:
|
|
549
|
+
jwt = _context.v;
|
|
550
|
+
_context.n = 3;
|
|
551
|
+
return fetch('https://oauth2.googleapis.com/token', {
|
|
552
|
+
method: 'POST',
|
|
553
|
+
headers: {
|
|
554
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
555
|
+
},
|
|
556
|
+
body: new URLSearchParams({
|
|
557
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
|
558
|
+
assertion: jwt
|
|
559
|
+
})
|
|
560
|
+
});
|
|
561
|
+
case 3:
|
|
562
|
+
response = _context.v;
|
|
563
|
+
if (response.ok) {
|
|
564
|
+
_context.n = 5;
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
_context.n = 4;
|
|
568
|
+
return response.text();
|
|
569
|
+
case 4:
|
|
570
|
+
error = _context.v;
|
|
571
|
+
throw new Error("Failed to get access token: ".concat(error));
|
|
572
|
+
case 5:
|
|
573
|
+
_context.n = 6;
|
|
574
|
+
return response.json();
|
|
575
|
+
case 6:
|
|
576
|
+
data = _context.v;
|
|
577
|
+
this.accessToken = data.access_token;
|
|
578
|
+
this.tokenExpiry = Date.now() + (data.expires_in - 60) * 1000; // Refresh 1 min early
|
|
579
|
+
return _context.a(2, this.accessToken);
|
|
580
|
+
}
|
|
581
|
+
}, _callee, this);
|
|
582
|
+
}));
|
|
583
|
+
function getAccessToken() {
|
|
584
|
+
return _getAccessToken.apply(this, arguments);
|
|
585
|
+
}
|
|
586
|
+
return getAccessToken;
|
|
587
|
+
}()
|
|
588
|
+
/**
|
|
589
|
+
* Create JWT for Service Account auth
|
|
590
|
+
*/
|
|
591
|
+
)
|
|
592
|
+
}, {
|
|
593
|
+
key: "createJWT",
|
|
594
|
+
value: (function () {
|
|
595
|
+
var _createJWT = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(header, payload, privateKey) {
|
|
596
|
+
var headerB64, payloadB64, signatureInput, pemHeader, pemFooter, pemContents, crypto, sign, signature, signatureB64;
|
|
597
|
+
return _regenerator().w(function (_context2) {
|
|
598
|
+
while (1) switch (_context2.n) {
|
|
599
|
+
case 0:
|
|
600
|
+
new TextEncoder();
|
|
601
|
+
headerB64 = this.base64UrlEncode(JSON.stringify(header));
|
|
602
|
+
payloadB64 = this.base64UrlEncode(JSON.stringify(payload));
|
|
603
|
+
signatureInput = "".concat(headerB64, ".").concat(payloadB64); // Import private key
|
|
604
|
+
pemHeader = '-----BEGIN PRIVATE KEY-----';
|
|
605
|
+
pemFooter = '-----END PRIVATE KEY-----';
|
|
606
|
+
pemContents = privateKey.replace(pemHeader, '').replace(pemFooter, '').replace(/\s/g, '');
|
|
607
|
+
Buffer.from(pemContents, 'base64'); // Use Node.js crypto for signing
|
|
608
|
+
_context2.n = 1;
|
|
609
|
+
return import('crypto');
|
|
610
|
+
case 1:
|
|
611
|
+
crypto = _context2.v;
|
|
612
|
+
sign = crypto.createSign('RSA-SHA256');
|
|
613
|
+
sign.update(signatureInput);
|
|
614
|
+
sign.end();
|
|
615
|
+
signature = sign.sign({
|
|
616
|
+
key: privateKey,
|
|
617
|
+
padding: crypto.constants.RSA_PKCS1_PADDING
|
|
618
|
+
});
|
|
619
|
+
signatureB64 = this.base64UrlEncode(signature);
|
|
620
|
+
return _context2.a(2, "".concat(signatureInput, ".").concat(signatureB64));
|
|
621
|
+
}
|
|
622
|
+
}, _callee2, this);
|
|
623
|
+
}));
|
|
624
|
+
function createJWT(_x, _x2, _x3) {
|
|
625
|
+
return _createJWT.apply(this, arguments);
|
|
626
|
+
}
|
|
627
|
+
return createJWT;
|
|
628
|
+
}())
|
|
629
|
+
}, {
|
|
630
|
+
key: "base64UrlEncode",
|
|
631
|
+
value: function base64UrlEncode(data) {
|
|
632
|
+
typeof data === 'string' ? data : Buffer.from(data).toString('base64');
|
|
633
|
+
return Buffer.from(data).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Make authenticated request to Sheets API
|
|
638
|
+
*/
|
|
639
|
+
}, {
|
|
640
|
+
key: "request",
|
|
641
|
+
value: (function () {
|
|
642
|
+
var _request = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(endpoint) {
|
|
643
|
+
var options,
|
|
644
|
+
token,
|
|
645
|
+
baseUrl,
|
|
646
|
+
response,
|
|
647
|
+
error,
|
|
648
|
+
text,
|
|
649
|
+
_args3 = arguments;
|
|
650
|
+
return _regenerator().w(function (_context3) {
|
|
651
|
+
while (1) switch (_context3.n) {
|
|
652
|
+
case 0:
|
|
653
|
+
options = _args3.length > 1 && _args3[1] !== undefined ? _args3[1] : {};
|
|
654
|
+
_context3.n = 1;
|
|
655
|
+
return this.getAccessToken();
|
|
656
|
+
case 1:
|
|
657
|
+
token = _context3.v;
|
|
658
|
+
baseUrl = 'https://sheets.googleapis.com/v4/spreadsheets';
|
|
659
|
+
_context3.n = 2;
|
|
660
|
+
return fetch("".concat(baseUrl, "/").concat(this.spreadsheetId).concat(endpoint), _objectSpread2(_objectSpread2({}, options), {}, {
|
|
661
|
+
headers: _objectSpread2({
|
|
662
|
+
'Authorization': "Bearer ".concat(token),
|
|
663
|
+
'Content-Type': 'application/json'
|
|
664
|
+
}, options.headers)
|
|
665
|
+
}));
|
|
666
|
+
case 2:
|
|
667
|
+
response = _context3.v;
|
|
668
|
+
if (response.ok) {
|
|
669
|
+
_context3.n = 4;
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
_context3.n = 3;
|
|
673
|
+
return response.text();
|
|
674
|
+
case 3:
|
|
675
|
+
error = _context3.v;
|
|
676
|
+
throw new Error("Sheets API error (".concat(response.status, "): ").concat(error));
|
|
677
|
+
case 4:
|
|
678
|
+
_context3.n = 5;
|
|
679
|
+
return response.text();
|
|
680
|
+
case 5:
|
|
681
|
+
text = _context3.v;
|
|
682
|
+
return _context3.a(2, text ? JSON.parse(text) : null);
|
|
683
|
+
}
|
|
684
|
+
}, _callee3, this);
|
|
685
|
+
}));
|
|
686
|
+
function request(_x4) {
|
|
687
|
+
return _request.apply(this, arguments);
|
|
688
|
+
}
|
|
689
|
+
return request;
|
|
690
|
+
}()
|
|
691
|
+
/**
|
|
692
|
+
* Append row to sheet
|
|
693
|
+
*/
|
|
694
|
+
)
|
|
695
|
+
}, {
|
|
696
|
+
key: "appendRow",
|
|
697
|
+
value: (function () {
|
|
698
|
+
var _appendRow = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(values) {
|
|
699
|
+
var sheetName,
|
|
700
|
+
range,
|
|
701
|
+
_args4 = arguments;
|
|
702
|
+
return _regenerator().w(function (_context4) {
|
|
703
|
+
while (1) switch (_context4.n) {
|
|
704
|
+
case 0:
|
|
705
|
+
sheetName = _args4.length > 1 && _args4[1] !== undefined ? _args4[1] : null;
|
|
706
|
+
range = "".concat(sheetName || this.sheetName, "!A:Z");
|
|
707
|
+
return _context4.a(2, this.request("/values/".concat(encodeURIComponent(range), ":append"), {
|
|
708
|
+
method: 'POST',
|
|
709
|
+
body: JSON.stringify({
|
|
710
|
+
values: [values],
|
|
711
|
+
majorDimension: 'ROWS'
|
|
712
|
+
}) + '?valueInputOption=RAW&insertDataOption=INSERT_ROWS'
|
|
713
|
+
}));
|
|
714
|
+
}
|
|
715
|
+
}, _callee4, this);
|
|
716
|
+
}));
|
|
717
|
+
function appendRow(_x5) {
|
|
718
|
+
return _appendRow.apply(this, arguments);
|
|
719
|
+
}
|
|
720
|
+
return appendRow;
|
|
721
|
+
}()
|
|
722
|
+
/**
|
|
723
|
+
* Get sheet values
|
|
724
|
+
*/
|
|
725
|
+
)
|
|
726
|
+
}, {
|
|
727
|
+
key: "getValues",
|
|
728
|
+
value: (function () {
|
|
729
|
+
var _getValues = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(range) {
|
|
730
|
+
return _regenerator().w(function (_context5) {
|
|
731
|
+
while (1) switch (_context5.n) {
|
|
732
|
+
case 0:
|
|
733
|
+
return _context5.a(2, this.request("/values/".concat(encodeURIComponent(range))));
|
|
734
|
+
}
|
|
735
|
+
}, _callee5, this);
|
|
736
|
+
}));
|
|
737
|
+
function getValues(_x6) {
|
|
738
|
+
return _getValues.apply(this, arguments);
|
|
739
|
+
}
|
|
740
|
+
return getValues;
|
|
741
|
+
}()
|
|
742
|
+
/**
|
|
743
|
+
* Update specific cell/range
|
|
744
|
+
*/
|
|
745
|
+
)
|
|
746
|
+
}, {
|
|
747
|
+
key: "updateValues",
|
|
748
|
+
value: (function () {
|
|
749
|
+
var _updateValues = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6(range, values) {
|
|
750
|
+
return _regenerator().w(function (_context6) {
|
|
751
|
+
while (1) switch (_context6.n) {
|
|
752
|
+
case 0:
|
|
753
|
+
return _context6.a(2, this.request("/values/".concat(encodeURIComponent(range), "?valueInputOption=RAW"), {
|
|
754
|
+
method: 'PUT',
|
|
755
|
+
body: JSON.stringify({
|
|
756
|
+
values: values,
|
|
757
|
+
majorDimension: 'ROWS'
|
|
758
|
+
})
|
|
759
|
+
}));
|
|
760
|
+
}
|
|
761
|
+
}, _callee6, this);
|
|
762
|
+
}));
|
|
763
|
+
function updateValues(_x7, _x8) {
|
|
764
|
+
return _updateValues.apply(this, arguments);
|
|
765
|
+
}
|
|
766
|
+
return updateValues;
|
|
767
|
+
}()
|
|
768
|
+
/**
|
|
769
|
+
* Check if headers exist, create if not
|
|
770
|
+
*/
|
|
771
|
+
)
|
|
772
|
+
}, {
|
|
773
|
+
key: "ensureHeaders",
|
|
774
|
+
value: (function () {
|
|
775
|
+
var _ensureHeaders = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7(headers) {
|
|
776
|
+
var existing, _t2;
|
|
777
|
+
return _regenerator().w(function (_context7) {
|
|
778
|
+
while (1) switch (_context7.p = _context7.n) {
|
|
779
|
+
case 0:
|
|
780
|
+
_context7.p = 0;
|
|
781
|
+
_context7.n = 1;
|
|
782
|
+
return this.getValues("".concat(this.sheetName, "!1:1"));
|
|
783
|
+
case 1:
|
|
784
|
+
existing = _context7.v;
|
|
785
|
+
if (!(!existing.values || existing.values.length === 0)) {
|
|
786
|
+
_context7.n = 3;
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
_context7.n = 2;
|
|
790
|
+
return this.updateValues("".concat(this.sheetName, "!A1"), [headers]);
|
|
791
|
+
case 2:
|
|
792
|
+
return _context7.a(2, {
|
|
793
|
+
created: true
|
|
794
|
+
});
|
|
795
|
+
case 3:
|
|
796
|
+
return _context7.a(2, {
|
|
797
|
+
created: false,
|
|
798
|
+
existing: existing.values[0]
|
|
799
|
+
});
|
|
800
|
+
case 4:
|
|
801
|
+
_context7.p = 4;
|
|
802
|
+
_context7.v;
|
|
803
|
+
_context7.p = 5;
|
|
804
|
+
_context7.n = 6;
|
|
805
|
+
return this.updateValues("".concat(this.sheetName, "!A1"), [headers]);
|
|
806
|
+
case 6:
|
|
807
|
+
return _context7.a(2, {
|
|
808
|
+
created: true
|
|
809
|
+
});
|
|
810
|
+
case 7:
|
|
811
|
+
_context7.p = 7;
|
|
812
|
+
_t2 = _context7.v;
|
|
813
|
+
throw new Error("Failed to set up headers: ".concat(_t2.message));
|
|
814
|
+
case 8:
|
|
815
|
+
return _context7.a(2);
|
|
816
|
+
}
|
|
817
|
+
}, _callee7, this, [[5, 7], [0, 4]]);
|
|
818
|
+
}));
|
|
819
|
+
function ensureHeaders(_x9) {
|
|
820
|
+
return _ensureHeaders.apply(this, arguments);
|
|
821
|
+
}
|
|
822
|
+
return ensureHeaders;
|
|
823
|
+
}()
|
|
824
|
+
/**
|
|
825
|
+
* Find row by feedback ID
|
|
826
|
+
*/
|
|
827
|
+
)
|
|
828
|
+
}, {
|
|
829
|
+
key: "findRowByFeedbackId",
|
|
830
|
+
value: (function () {
|
|
831
|
+
var _findRowByFeedbackId = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee8(feedbackId) {
|
|
832
|
+
var idColumn,
|
|
833
|
+
values,
|
|
834
|
+
rowIndex,
|
|
835
|
+
_args8 = arguments;
|
|
836
|
+
return _regenerator().w(function (_context8) {
|
|
837
|
+
while (1) switch (_context8.n) {
|
|
838
|
+
case 0:
|
|
839
|
+
idColumn = _args8.length > 1 && _args8[1] !== undefined ? _args8[1] : 'B';
|
|
840
|
+
_context8.n = 1;
|
|
841
|
+
return this.getValues("".concat(this.sheetName, "!").concat(idColumn, ":").concat(idColumn));
|
|
842
|
+
case 1:
|
|
843
|
+
values = _context8.v;
|
|
844
|
+
if (values.values) {
|
|
845
|
+
_context8.n = 2;
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
return _context8.a(2, null);
|
|
849
|
+
case 2:
|
|
850
|
+
rowIndex = values.values.findIndex(function (row) {
|
|
851
|
+
return row[0] === feedbackId;
|
|
852
|
+
});
|
|
853
|
+
return _context8.a(2, rowIndex >= 0 ? rowIndex + 1 : null);
|
|
854
|
+
}
|
|
855
|
+
}, _callee8, this);
|
|
856
|
+
}));
|
|
857
|
+
function findRowByFeedbackId(_x0) {
|
|
858
|
+
return _findRowByFeedbackId.apply(this, arguments);
|
|
859
|
+
}
|
|
860
|
+
return findRowByFeedbackId;
|
|
861
|
+
}()
|
|
862
|
+
/**
|
|
863
|
+
* Update feedback status in sheet
|
|
864
|
+
*/
|
|
865
|
+
)
|
|
866
|
+
}, {
|
|
867
|
+
key: "updateStatus",
|
|
868
|
+
value: (function () {
|
|
869
|
+
var _updateStatus = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee9(feedbackId, newStatus) {
|
|
870
|
+
var statusColumn,
|
|
871
|
+
rowNumber,
|
|
872
|
+
_args9 = arguments;
|
|
873
|
+
return _regenerator().w(function (_context9) {
|
|
874
|
+
while (1) switch (_context9.n) {
|
|
875
|
+
case 0:
|
|
876
|
+
statusColumn = _args9.length > 2 && _args9[2] !== undefined ? _args9[2] : 'E';
|
|
877
|
+
_context9.n = 1;
|
|
878
|
+
return this.findRowByFeedbackId(feedbackId);
|
|
879
|
+
case 1:
|
|
880
|
+
rowNumber = _context9.v;
|
|
881
|
+
if (rowNumber) {
|
|
882
|
+
_context9.n = 2;
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
throw new Error("Feedback ".concat(feedbackId, " not found in sheet"));
|
|
886
|
+
case 2:
|
|
887
|
+
_context9.n = 3;
|
|
888
|
+
return this.updateValues("".concat(this.sheetName, "!").concat(statusColumn).concat(rowNumber), [[newStatus]]);
|
|
889
|
+
case 3:
|
|
890
|
+
return _context9.a(2, {
|
|
891
|
+
success: true,
|
|
892
|
+
row: rowNumber
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
}, _callee9, this);
|
|
896
|
+
}));
|
|
897
|
+
function updateStatus(_x1, _x10) {
|
|
898
|
+
return _updateStatus.apply(this, arguments);
|
|
899
|
+
}
|
|
900
|
+
return updateStatus;
|
|
901
|
+
}())
|
|
902
|
+
}]);
|
|
903
|
+
}(); // ============================================
|
|
904
|
+
// OAUTH CLIENT (User Authentication)
|
|
905
|
+
// ============================================
|
|
906
|
+
var SheetsOAuthClient = /*#__PURE__*/function (_SheetsClient) {
|
|
907
|
+
function SheetsOAuthClient(config) {
|
|
908
|
+
var _this;
|
|
909
|
+
_classCallCheck(this, SheetsOAuthClient);
|
|
910
|
+
_this = _callSuper(this, SheetsOAuthClient, [{
|
|
911
|
+
spreadsheetId: config.spreadsheetId,
|
|
912
|
+
sheetName: config.sheetName,
|
|
913
|
+
credentials: {
|
|
914
|
+
client_email: '',
|
|
915
|
+
private_key: ''
|
|
916
|
+
}
|
|
917
|
+
}]);
|
|
918
|
+
_this.clientId = config.clientId || process.env.GOOGLE_CLIENT_ID;
|
|
919
|
+
_this.clientSecret = config.clientSecret || process.env.GOOGLE_CLIENT_SECRET;
|
|
920
|
+
_this.redirectUri = config.redirectUri || process.env.GOOGLE_REDIRECT_URI;
|
|
921
|
+
|
|
922
|
+
// Token storage (user must provide storage mechanism)
|
|
923
|
+
_this.getStoredTokens = config.getStoredTokens;
|
|
924
|
+
_this.saveTokens = config.saveTokens;
|
|
925
|
+
if (!_this.clientId || !_this.clientSecret) {
|
|
926
|
+
throw new Error('OAuth configuration missing. Required: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET');
|
|
927
|
+
}
|
|
928
|
+
return _this;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Get OAuth authorization URL
|
|
933
|
+
*/
|
|
934
|
+
_inherits(SheetsOAuthClient, _SheetsClient);
|
|
935
|
+
return _createClass(SheetsOAuthClient, [{
|
|
936
|
+
key: "getAuthUrl",
|
|
937
|
+
value: function getAuthUrl() {
|
|
938
|
+
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
939
|
+
var params = new URLSearchParams({
|
|
940
|
+
client_id: this.clientId,
|
|
941
|
+
redirect_uri: this.redirectUri,
|
|
942
|
+
response_type: 'code',
|
|
943
|
+
scope: 'https://www.googleapis.com/auth/spreadsheets',
|
|
944
|
+
access_type: 'offline',
|
|
945
|
+
prompt: 'consent',
|
|
946
|
+
state: state
|
|
947
|
+
});
|
|
948
|
+
return "https://accounts.google.com/o/oauth2/v2/auth?".concat(params);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Exchange authorization code for tokens
|
|
953
|
+
*/
|
|
954
|
+
}, {
|
|
955
|
+
key: "exchangeCode",
|
|
956
|
+
value: (function () {
|
|
957
|
+
var _exchangeCode = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee0(code) {
|
|
958
|
+
var response, error, tokens;
|
|
959
|
+
return _regenerator().w(function (_context0) {
|
|
960
|
+
while (1) switch (_context0.n) {
|
|
961
|
+
case 0:
|
|
962
|
+
_context0.n = 1;
|
|
963
|
+
return fetch('https://oauth2.googleapis.com/token', {
|
|
964
|
+
method: 'POST',
|
|
965
|
+
headers: {
|
|
966
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
967
|
+
},
|
|
968
|
+
body: new URLSearchParams({
|
|
969
|
+
client_id: this.clientId,
|
|
970
|
+
client_secret: this.clientSecret,
|
|
971
|
+
code: code,
|
|
972
|
+
grant_type: 'authorization_code',
|
|
973
|
+
redirect_uri: this.redirectUri
|
|
974
|
+
})
|
|
975
|
+
});
|
|
976
|
+
case 1:
|
|
977
|
+
response = _context0.v;
|
|
978
|
+
if (response.ok) {
|
|
979
|
+
_context0.n = 3;
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
_context0.n = 2;
|
|
983
|
+
return response.text();
|
|
984
|
+
case 2:
|
|
985
|
+
error = _context0.v;
|
|
986
|
+
throw new Error("Failed to exchange code: ".concat(error));
|
|
987
|
+
case 3:
|
|
988
|
+
_context0.n = 4;
|
|
989
|
+
return response.json();
|
|
990
|
+
case 4:
|
|
991
|
+
tokens = _context0.v;
|
|
992
|
+
if (!this.saveTokens) {
|
|
993
|
+
_context0.n = 5;
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
996
|
+
_context0.n = 5;
|
|
997
|
+
return this.saveTokens({
|
|
998
|
+
access_token: tokens.access_token,
|
|
999
|
+
refresh_token: tokens.refresh_token,
|
|
1000
|
+
expiry: Date.now() + tokens.expires_in * 1000
|
|
1001
|
+
});
|
|
1002
|
+
case 5:
|
|
1003
|
+
return _context0.a(2, tokens);
|
|
1004
|
+
}
|
|
1005
|
+
}, _callee0, this);
|
|
1006
|
+
}));
|
|
1007
|
+
function exchangeCode(_x11) {
|
|
1008
|
+
return _exchangeCode.apply(this, arguments);
|
|
1009
|
+
}
|
|
1010
|
+
return exchangeCode;
|
|
1011
|
+
}()
|
|
1012
|
+
/**
|
|
1013
|
+
* Refresh access token using refresh token
|
|
1014
|
+
*/
|
|
1015
|
+
)
|
|
1016
|
+
}, {
|
|
1017
|
+
key: "refreshAccessToken",
|
|
1018
|
+
value: (function () {
|
|
1019
|
+
var _refreshAccessToken = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee1(refreshToken) {
|
|
1020
|
+
var response, error, data, stored;
|
|
1021
|
+
return _regenerator().w(function (_context1) {
|
|
1022
|
+
while (1) switch (_context1.n) {
|
|
1023
|
+
case 0:
|
|
1024
|
+
_context1.n = 1;
|
|
1025
|
+
return fetch('https://oauth2.googleapis.com/token', {
|
|
1026
|
+
method: 'POST',
|
|
1027
|
+
headers: {
|
|
1028
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
1029
|
+
},
|
|
1030
|
+
body: new URLSearchParams({
|
|
1031
|
+
client_id: this.clientId,
|
|
1032
|
+
client_secret: this.clientSecret,
|
|
1033
|
+
refresh_token: refreshToken,
|
|
1034
|
+
grant_type: 'refresh_token'
|
|
1035
|
+
})
|
|
1036
|
+
});
|
|
1037
|
+
case 1:
|
|
1038
|
+
response = _context1.v;
|
|
1039
|
+
if (response.ok) {
|
|
1040
|
+
_context1.n = 3;
|
|
1041
|
+
break;
|
|
1042
|
+
}
|
|
1043
|
+
_context1.n = 2;
|
|
1044
|
+
return response.text();
|
|
1045
|
+
case 2:
|
|
1046
|
+
error = _context1.v;
|
|
1047
|
+
throw new Error("Failed to refresh token: ".concat(error));
|
|
1048
|
+
case 3:
|
|
1049
|
+
_context1.n = 4;
|
|
1050
|
+
return response.json();
|
|
1051
|
+
case 4:
|
|
1052
|
+
data = _context1.v;
|
|
1053
|
+
if (!this.saveTokens) {
|
|
1054
|
+
_context1.n = 6;
|
|
1055
|
+
break;
|
|
1056
|
+
}
|
|
1057
|
+
_context1.n = 5;
|
|
1058
|
+
return this.getStoredTokens();
|
|
1059
|
+
case 5:
|
|
1060
|
+
stored = _context1.v;
|
|
1061
|
+
_context1.n = 6;
|
|
1062
|
+
return this.saveTokens({
|
|
1063
|
+
access_token: data.access_token,
|
|
1064
|
+
refresh_token: stored.refresh_token,
|
|
1065
|
+
// Keep existing refresh token
|
|
1066
|
+
expiry: Date.now() + data.expires_in * 1000
|
|
1067
|
+
});
|
|
1068
|
+
case 6:
|
|
1069
|
+
return _context1.a(2, data.access_token);
|
|
1070
|
+
}
|
|
1071
|
+
}, _callee1, this);
|
|
1072
|
+
}));
|
|
1073
|
+
function refreshAccessToken(_x12) {
|
|
1074
|
+
return _refreshAccessToken.apply(this, arguments);
|
|
1075
|
+
}
|
|
1076
|
+
return refreshAccessToken;
|
|
1077
|
+
}()
|
|
1078
|
+
/**
|
|
1079
|
+
* Override getAccessToken to use OAuth
|
|
1080
|
+
*/
|
|
1081
|
+
)
|
|
1082
|
+
}, {
|
|
1083
|
+
key: "getAccessToken",
|
|
1084
|
+
value: (function () {
|
|
1085
|
+
var _getAccessToken2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee10() {
|
|
1086
|
+
var stored;
|
|
1087
|
+
return _regenerator().w(function (_context10) {
|
|
1088
|
+
while (1) switch (_context10.n) {
|
|
1089
|
+
case 0:
|
|
1090
|
+
if (this.getStoredTokens) {
|
|
1091
|
+
_context10.n = 1;
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
1094
|
+
throw new Error('Token storage not configured. Provide getStoredTokens and saveTokens functions.');
|
|
1095
|
+
case 1:
|
|
1096
|
+
_context10.n = 2;
|
|
1097
|
+
return this.getStoredTokens();
|
|
1098
|
+
case 2:
|
|
1099
|
+
stored = _context10.v;
|
|
1100
|
+
if (!(!stored || !stored.refresh_token)) {
|
|
1101
|
+
_context10.n = 3;
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
throw new Error('Not authenticated. User needs to complete OAuth flow.');
|
|
1105
|
+
case 3:
|
|
1106
|
+
if (!(stored.access_token && stored.expiry && Date.now() < stored.expiry - 60000)) {
|
|
1107
|
+
_context10.n = 4;
|
|
1108
|
+
break;
|
|
1109
|
+
}
|
|
1110
|
+
return _context10.a(2, stored.access_token);
|
|
1111
|
+
case 4:
|
|
1112
|
+
return _context10.a(2, this.refreshAccessToken(stored.refresh_token));
|
|
1113
|
+
}
|
|
1114
|
+
}, _callee10, this);
|
|
1115
|
+
}));
|
|
1116
|
+
function getAccessToken() {
|
|
1117
|
+
return _getAccessToken2.apply(this, arguments);
|
|
1118
|
+
}
|
|
1119
|
+
return getAccessToken;
|
|
1120
|
+
}())
|
|
1121
|
+
}]);
|
|
1122
|
+
}(SheetsClient); // ============================================
|
|
1123
|
+
// REQUEST HANDLERS
|
|
1124
|
+
// ============================================
|
|
1125
|
+
/**
|
|
1126
|
+
* Create Sheets handler with configuration
|
|
1127
|
+
*/
|
|
1128
|
+
function createSheetsHandler() {
|
|
1129
|
+
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
1130
|
+
var handler = /*#__PURE__*/function () {
|
|
1131
|
+
var _ref = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee11(req, res) {
|
|
1132
|
+
var body, _body$action, action, feedbackData, feedbackId, status, row, ClientClass, client, result, headers, errorResponse, _t3, _t4;
|
|
1133
|
+
return _regenerator().w(function (_context11) {
|
|
1134
|
+
while (1) switch (_context11.p = _context11.n) {
|
|
1135
|
+
case 0:
|
|
1136
|
+
_context11.p = 0;
|
|
1137
|
+
body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
|
|
1138
|
+
_body$action = body.action, action = _body$action === void 0 ? 'append' : _body$action, feedbackData = body.feedbackData, feedbackId = body.feedbackId, status = body.status, row = body.row; // Choose client type based on config
|
|
1139
|
+
ClientClass = config.oauth ? SheetsOAuthClient : SheetsClient;
|
|
1140
|
+
client = new ClientClass(config);
|
|
1141
|
+
_t3 = action;
|
|
1142
|
+
_context11.n = _t3 === 'append' ? 1 : _t3 === 'updateStatus' ? 3 : _t3 === 'updateRow' ? 5 : _t3 === 'getHeaders' ? 7 : _t3 === 'ensureHeaders' ? 8 : _t3 === 'getAuthUrl' ? 10 : _t3 === 'exchangeCode' ? 12 : 15;
|
|
1143
|
+
break;
|
|
1144
|
+
case 1:
|
|
1145
|
+
_context11.n = 2;
|
|
1146
|
+
return handleAppend(client, feedbackData, config);
|
|
1147
|
+
case 2:
|
|
1148
|
+
result = _context11.v;
|
|
1149
|
+
return _context11.a(3, 16);
|
|
1150
|
+
case 3:
|
|
1151
|
+
_context11.n = 4;
|
|
1152
|
+
return handleUpdateStatus(client, feedbackId, status, config);
|
|
1153
|
+
case 4:
|
|
1154
|
+
result = _context11.v;
|
|
1155
|
+
return _context11.a(3, 16);
|
|
1156
|
+
case 5:
|
|
1157
|
+
_context11.n = 6;
|
|
1158
|
+
return handleUpdateRow(client, row, feedbackData, config);
|
|
1159
|
+
case 6:
|
|
1160
|
+
result = _context11.v;
|
|
1161
|
+
return _context11.a(3, 16);
|
|
1162
|
+
case 7:
|
|
1163
|
+
result = {
|
|
1164
|
+
headers: getSheetHeaders(config)
|
|
1165
|
+
};
|
|
1166
|
+
return _context11.a(3, 16);
|
|
1167
|
+
case 8:
|
|
1168
|
+
headers = getSheetHeaders(config);
|
|
1169
|
+
_context11.n = 9;
|
|
1170
|
+
return client.ensureHeaders(headers);
|
|
1171
|
+
case 9:
|
|
1172
|
+
result = _context11.v;
|
|
1173
|
+
return _context11.a(3, 16);
|
|
1174
|
+
case 10:
|
|
1175
|
+
if (client instanceof SheetsOAuthClient) {
|
|
1176
|
+
_context11.n = 11;
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
throw new Error('OAuth not configured');
|
|
1180
|
+
case 11:
|
|
1181
|
+
result = {
|
|
1182
|
+
url: client.getAuthUrl(body.state)
|
|
1183
|
+
};
|
|
1184
|
+
return _context11.a(3, 16);
|
|
1185
|
+
case 12:
|
|
1186
|
+
if (client instanceof SheetsOAuthClient) {
|
|
1187
|
+
_context11.n = 13;
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
throw new Error('OAuth not configured');
|
|
1191
|
+
case 13:
|
|
1192
|
+
_context11.n = 14;
|
|
1193
|
+
return client.exchangeCode(body.code);
|
|
1194
|
+
case 14:
|
|
1195
|
+
result = _context11.v;
|
|
1196
|
+
return _context11.a(3, 16);
|
|
1197
|
+
case 15:
|
|
1198
|
+
throw new Error("Unknown action: ".concat(action));
|
|
1199
|
+
case 16:
|
|
1200
|
+
if (!(res !== null && res !== void 0 && res.json)) {
|
|
1201
|
+
_context11.n = 17;
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
res.status(200).json(result);
|
|
1205
|
+
_context11.n = 18;
|
|
1206
|
+
break;
|
|
1207
|
+
case 17:
|
|
1208
|
+
return _context11.a(2, new Response(JSON.stringify(result), {
|
|
1209
|
+
status: 200,
|
|
1210
|
+
headers: {
|
|
1211
|
+
'Content-Type': 'application/json'
|
|
1212
|
+
}
|
|
1213
|
+
}));
|
|
1214
|
+
case 18:
|
|
1215
|
+
_context11.n = 21;
|
|
1216
|
+
break;
|
|
1217
|
+
case 19:
|
|
1218
|
+
_context11.p = 19;
|
|
1219
|
+
_t4 = _context11.v;
|
|
1220
|
+
errorResponse = {
|
|
1221
|
+
success: false,
|
|
1222
|
+
error: _t4.message
|
|
1223
|
+
};
|
|
1224
|
+
if (!(res !== null && res !== void 0 && res.json)) {
|
|
1225
|
+
_context11.n = 20;
|
|
1226
|
+
break;
|
|
1227
|
+
}
|
|
1228
|
+
res.status(500).json(errorResponse);
|
|
1229
|
+
_context11.n = 21;
|
|
1230
|
+
break;
|
|
1231
|
+
case 20:
|
|
1232
|
+
return _context11.a(2, new Response(JSON.stringify(errorResponse), {
|
|
1233
|
+
status: 500,
|
|
1234
|
+
headers: {
|
|
1235
|
+
'Content-Type': 'application/json'
|
|
1236
|
+
}
|
|
1237
|
+
}));
|
|
1238
|
+
case 21:
|
|
1239
|
+
return _context11.a(2);
|
|
1240
|
+
}
|
|
1241
|
+
}, _callee11, null, [[0, 19]]);
|
|
1242
|
+
}));
|
|
1243
|
+
return function handler(_x13, _x14) {
|
|
1244
|
+
return _ref.apply(this, arguments);
|
|
1245
|
+
};
|
|
1246
|
+
}();
|
|
1247
|
+
return handler;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Handle append row
|
|
1252
|
+
*/
|
|
1253
|
+
function handleAppend(_x15, _x16, _x17) {
|
|
1254
|
+
return _handleAppend.apply(this, arguments);
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Handle status update
|
|
1258
|
+
*/
|
|
1259
|
+
function _handleAppend() {
|
|
1260
|
+
_handleAppend = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee14(client, feedbackData, config) {
|
|
1261
|
+
var _result$updates, _result$updates2;
|
|
1262
|
+
var headers, row, result;
|
|
1263
|
+
return _regenerator().w(function (_context14) {
|
|
1264
|
+
while (1) switch (_context14.n) {
|
|
1265
|
+
case 0:
|
|
1266
|
+
// Ensure headers exist
|
|
1267
|
+
headers = getSheetHeaders(config);
|
|
1268
|
+
_context14.n = 1;
|
|
1269
|
+
return client.ensureHeaders(headers);
|
|
1270
|
+
case 1:
|
|
1271
|
+
// Transform feedback to row
|
|
1272
|
+
row = feedbackToSheetRow(feedbackData, config); // Append row
|
|
1273
|
+
_context14.n = 2;
|
|
1274
|
+
return client.appendRow(row);
|
|
1275
|
+
case 2:
|
|
1276
|
+
result = _context14.v;
|
|
1277
|
+
return _context14.a(2, {
|
|
1278
|
+
success: true,
|
|
1279
|
+
updatedRange: result === null || result === void 0 || (_result$updates = result.updates) === null || _result$updates === void 0 ? void 0 : _result$updates.updatedRange,
|
|
1280
|
+
rowNumber: result === null || result === void 0 || (_result$updates2 = result.updates) === null || _result$updates2 === void 0 || (_result$updates2 = _result$updates2.updatedRange) === null || _result$updates2 === void 0 || (_result$updates2 = _result$updates2.match(/:(\d+)$/)) === null || _result$updates2 === void 0 ? void 0 : _result$updates2[1]
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
}, _callee14);
|
|
1284
|
+
}));
|
|
1285
|
+
return _handleAppend.apply(this, arguments);
|
|
1286
|
+
}
|
|
1287
|
+
function handleUpdateStatus(_x18, _x19, _x20, _x21) {
|
|
1288
|
+
return _handleUpdateStatus.apply(this, arguments);
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Handle row update
|
|
1292
|
+
*/
|
|
1293
|
+
function _handleUpdateStatus() {
|
|
1294
|
+
_handleUpdateStatus = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee15(client, feedbackId, status, config) {
|
|
1295
|
+
var _mergeSheetColumns, order, statusIndex, statusColumn, result;
|
|
1296
|
+
return _regenerator().w(function (_context15) {
|
|
1297
|
+
while (1) switch (_context15.n) {
|
|
1298
|
+
case 0:
|
|
1299
|
+
// Find status column
|
|
1300
|
+
_mergeSheetColumns = mergeSheetColumns(config.columns, config.columnOrder), _mergeSheetColumns.columns, order = _mergeSheetColumns.order;
|
|
1301
|
+
statusIndex = order.indexOf('status');
|
|
1302
|
+
statusColumn = String.fromCharCode(65 + statusIndex); // A, B, C...
|
|
1303
|
+
_context15.n = 1;
|
|
1304
|
+
return client.updateStatus(feedbackId, status, statusColumn);
|
|
1305
|
+
case 1:
|
|
1306
|
+
result = _context15.v;
|
|
1307
|
+
return _context15.a(2, result);
|
|
1308
|
+
}
|
|
1309
|
+
}, _callee15);
|
|
1310
|
+
}));
|
|
1311
|
+
return _handleUpdateStatus.apply(this, arguments);
|
|
1312
|
+
}
|
|
1313
|
+
function handleUpdateRow(_x22, _x23, _x24, _x25) {
|
|
1314
|
+
return _handleUpdateRow.apply(this, arguments);
|
|
1315
|
+
} // ============================================
|
|
1316
|
+
// FRAMEWORK-SPECIFIC EXPORTS
|
|
1317
|
+
// ============================================
|
|
1318
|
+
/**
|
|
1319
|
+
* Next.js App Router handler
|
|
1320
|
+
* Returns a function that can be directly exported as POST
|
|
1321
|
+
*/
|
|
1322
|
+
function _handleUpdateRow() {
|
|
1323
|
+
_handleUpdateRow = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee16(client, rowNumber, feedbackData, config) {
|
|
1324
|
+
var row, range;
|
|
1325
|
+
return _regenerator().w(function (_context16) {
|
|
1326
|
+
while (1) switch (_context16.n) {
|
|
1327
|
+
case 0:
|
|
1328
|
+
row = feedbackToSheetRow(feedbackData, config);
|
|
1329
|
+
range = "".concat(config.sheetName || 'Feedback', "!A").concat(rowNumber);
|
|
1330
|
+
_context16.n = 1;
|
|
1331
|
+
return client.updateValues(range, [row]);
|
|
1332
|
+
case 1:
|
|
1333
|
+
return _context16.a(2, {
|
|
1334
|
+
success: true,
|
|
1335
|
+
row: rowNumber
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
}, _callee16);
|
|
1339
|
+
}));
|
|
1340
|
+
return _handleUpdateRow.apply(this, arguments);
|
|
1341
|
+
}
|
|
1342
|
+
function createNextAppHandler() {
|
|
1343
|
+
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
1344
|
+
var handler = createSheetsHandler(config);
|
|
1345
|
+
return /*#__PURE__*/function () {
|
|
1346
|
+
var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee12(request) {
|
|
1347
|
+
var body;
|
|
1348
|
+
return _regenerator().w(function (_context12) {
|
|
1349
|
+
while (1) switch (_context12.n) {
|
|
1350
|
+
case 0:
|
|
1351
|
+
_context12.n = 1;
|
|
1352
|
+
return request.json();
|
|
1353
|
+
case 1:
|
|
1354
|
+
body = _context12.v;
|
|
1355
|
+
return _context12.a(2, handler({
|
|
1356
|
+
body: body
|
|
1357
|
+
}, null));
|
|
1358
|
+
}
|
|
1359
|
+
}, _callee12);
|
|
1360
|
+
}));
|
|
1361
|
+
return function (_x26) {
|
|
1362
|
+
return _ref2.apply(this, arguments);
|
|
1363
|
+
};
|
|
1364
|
+
}();
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* Next.js Pages Router handler
|
|
1369
|
+
*/
|
|
1370
|
+
function createNextPagesHandler() {
|
|
1371
|
+
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
1372
|
+
return createSheetsHandler(config);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* Express middleware
|
|
1377
|
+
*/
|
|
1378
|
+
function createExpressMiddleware() {
|
|
1379
|
+
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
1380
|
+
var handler = createSheetsHandler(config);
|
|
1381
|
+
return /*#__PURE__*/function () {
|
|
1382
|
+
var _ref3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee13(req, res, next) {
|
|
1383
|
+
var _t5;
|
|
1384
|
+
return _regenerator().w(function (_context13) {
|
|
1385
|
+
while (1) switch (_context13.p = _context13.n) {
|
|
1386
|
+
case 0:
|
|
1387
|
+
_context13.p = 0;
|
|
1388
|
+
_context13.n = 1;
|
|
1389
|
+
return handler(req, res);
|
|
1390
|
+
case 1:
|
|
1391
|
+
_context13.n = 3;
|
|
1392
|
+
break;
|
|
1393
|
+
case 2:
|
|
1394
|
+
_context13.p = 2;
|
|
1395
|
+
_t5 = _context13.v;
|
|
1396
|
+
next(_t5);
|
|
1397
|
+
case 3:
|
|
1398
|
+
return _context13.a(2);
|
|
1399
|
+
}
|
|
1400
|
+
}, _callee13, null, [[0, 2]]);
|
|
1401
|
+
}));
|
|
1402
|
+
return function (_x27, _x28, _x29) {
|
|
1403
|
+
return _ref3.apply(this, arguments);
|
|
1404
|
+
};
|
|
1405
|
+
}();
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// ============================================
|
|
1409
|
+
// GOOGLE APPS SCRIPT TEMPLATE
|
|
1410
|
+
// ============================================
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* Returns the Google Apps Script code that users paste into their sheet
|
|
1414
|
+
*/
|
|
1415
|
+
function getAppsScriptTemplate() {
|
|
1416
|
+
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
1417
|
+
var headers = getSheetHeaders(config);
|
|
1418
|
+
return "\n/**\n * React Visual Feedback - Google Apps Script Handler\n *\n * Setup:\n * 1. Open your Google Sheet\n * 2. Extensions > Apps Script\n * 3. Paste this code\n * 4. Deploy > New deployment > Web app\n * 5. Execute as: Me, Who has access: Anyone\n * 6. Copy the URL and use in your React app\n */\n\n// Sheet configuration\nconst SHEET_NAME = '".concat(config.sheetName || 'Feedback', "';\nconst HEADERS = ").concat(JSON.stringify(headers), ";\n\n/**\n * Handle POST requests\n */\nfunction doPost(e) {\n try {\n const data = JSON.parse(e.postData.contents);\n const action = data.action || 'append';\n\n let result;\n\n switch (action) {\n case 'append':\n result = appendFeedback(data.feedbackData);\n break;\n case 'updateStatus':\n result = updateStatus(data.feedbackId, data.status);\n break;\n case 'getRows':\n result = getRows(data.limit || 100);\n break;\n default:\n throw new Error('Unknown action: ' + action);\n }\n\n return ContentService\n .createTextOutput(JSON.stringify(result))\n .setMimeType(ContentService.MimeType.JSON);\n\n } catch (error) {\n return ContentService\n .createTextOutput(JSON.stringify({ success: false, error: error.message }))\n .setMimeType(ContentService.MimeType.JSON);\n }\n}\n\n/**\n * Handle GET requests (for testing)\n */\nfunction doGet(e) {\n return ContentService\n .createTextOutput(JSON.stringify({ status: 'ok', message: 'Feedback API ready' }))\n .setMimeType(ContentService.MimeType.JSON);\n}\n\n/**\n * Append feedback to sheet\n */\nfunction appendFeedback(feedbackData) {\n const sheet = getOrCreateSheet();\n\n const row = [\n new Date().toISOString(), // Timestamp\n feedbackData.id || '', // ID\n feedbackData.feedback || '', // Feedback\n feedbackData.type || 'bug', // Type\n feedbackData.status || 'new', // Status\n feedbackData.userName || 'Anonymous', // User Name\n feedbackData.userEmail || '', // User Email\n feedbackData.url || '', // Page URL\n feedbackData.viewport ? feedbackData.viewport.width + 'x' + feedbackData.viewport.height : '', // Viewport\n feedbackData.screenshot ? 'Yes' : 'No', // Screenshot\n feedbackData.video ? 'Yes' : 'No', // Video\n feedbackData.jiraKey || '' // Jira Key\n ];\n\n sheet.appendRow(row);\n\n return {\n success: true,\n row: sheet.getLastRow()\n };\n}\n\n/**\n * Update feedback status\n */\nfunction updateStatus(feedbackId, newStatus) {\n const sheet = getOrCreateSheet();\n const data = sheet.getDataRange().getValues();\n\n // Find row with matching ID (column B, index 1)\n for (let i = 1; i < data.length; i++) {\n if (data[i][1] === feedbackId) {\n // Update status (column E, index 4)\n sheet.getRange(i + 1, 5).setValue(newStatus);\n return { success: true, row: i + 1 };\n }\n }\n\n return { success: false, error: 'Feedback not found' };\n}\n\n/**\n * Get rows from sheet\n */\nfunction getRows(limit) {\n const sheet = getOrCreateSheet();\n const data = sheet.getDataRange().getValues();\n\n const rows = data.slice(1, limit + 1).map((row, index) => ({\n rowNumber: index + 2,\n timestamp: row[0],\n id: row[1],\n feedback: row[2],\n type: row[3],\n status: row[4],\n userName: row[5],\n userEmail: row[6],\n url: row[7],\n viewport: row[8],\n hasScreenshot: row[9] === 'Yes',\n hasVideo: row[10] === 'Yes',\n jiraKey: row[11]\n }));\n\n return { success: true, rows };\n}\n\n/**\n * Get or create the feedback sheet\n */\nfunction getOrCreateSheet() {\n const ss = SpreadsheetApp.getActiveSpreadsheet();\n let sheet = ss.getSheetByName(SHEET_NAME);\n\n if (!sheet) {\n sheet = ss.insertSheet(SHEET_NAME);\n sheet.appendRow(HEADERS);\n\n // Format headers\n const headerRange = sheet.getRange(1, 1, 1, HEADERS.length);\n headerRange.setFontWeight('bold');\n headerRange.setBackground('#4285f4');\n headerRange.setFontColor('#ffffff');\n }\n\n return sheet;\n}\n\n/**\n * Update Jira key for a feedback item\n */\nfunction updateJiraKey(feedbackId, jiraKey) {\n const sheet = getOrCreateSheet();\n const data = sheet.getDataRange().getValues();\n\n for (let i = 1; i < data.length; i++) {\n if (data[i][1] === feedbackId) {\n // Update Jira key (column L, index 11)\n sheet.getRange(i + 1, 12).setValue(jiraKey);\n return { success: true, row: i + 1 };\n }\n }\n\n return { success: false, error: 'Feedback not found' };\n}\n").trim();
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// ============================================
|
|
1422
|
+
// ZAPIER / WEBHOOK FORMATTERS
|
|
1423
|
+
// ============================================
|
|
1424
|
+
|
|
1425
|
+
/**
|
|
1426
|
+
* Format feedback for Zapier webhook
|
|
1427
|
+
*/
|
|
1428
|
+
function formatForZapier(feedbackData) {
|
|
1429
|
+
var _feedbackData$element, _feedbackData$element2;
|
|
1430
|
+
return {
|
|
1431
|
+
timestamp: new Date().toISOString(),
|
|
1432
|
+
id: feedbackData.id,
|
|
1433
|
+
feedback: feedbackData.feedback,
|
|
1434
|
+
type: feedbackData.type || 'bug',
|
|
1435
|
+
status: feedbackData.status || 'new',
|
|
1436
|
+
user_name: feedbackData.userName || 'Anonymous',
|
|
1437
|
+
user_email: feedbackData.userEmail || '',
|
|
1438
|
+
page_url: feedbackData.url || '',
|
|
1439
|
+
viewport: feedbackData.viewport ? "".concat(feedbackData.viewport.width, "x").concat(feedbackData.viewport.height) : '',
|
|
1440
|
+
has_screenshot: !!feedbackData.screenshot,
|
|
1441
|
+
has_video: !!feedbackData.video,
|
|
1442
|
+
element_selector: ((_feedbackData$element = feedbackData.elementInfo) === null || _feedbackData$element === void 0 ? void 0 : _feedbackData$element.selector) || '',
|
|
1443
|
+
component_name: (((_feedbackData$element2 = feedbackData.elementInfo) === null || _feedbackData$element2 === void 0 ? void 0 : _feedbackData$element2.componentStack) || [])[0] || ''
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
export { createExpressMiddleware, createNextAppHandler, createNextPagesHandler, createSheetsHandler, createSheetsHandler as default, formatForZapier, getAppsScriptTemplate };
|
|
1448
|
+
//# sourceMappingURL=sheets.js.map
|