@shopify/shop-minis-react 0.4.5 → 0.4.7
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/eslint/config.cjs
CHANGED
|
@@ -39,7 +39,12 @@ module.exports = {
|
|
|
39
39
|
// Shop Minis custom rules
|
|
40
40
|
'shop-minis/no-env-without-fallback': 'error',
|
|
41
41
|
'shop-minis/no-internal-imports': 'error',
|
|
42
|
-
'shop-minis/no-secrets':
|
|
42
|
+
'shop-minis/no-secrets': [
|
|
43
|
+
'error',
|
|
44
|
+
{
|
|
45
|
+
ignoreContent: ['^data:', '^gid://'],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
43
48
|
'shop-minis/prefer-sdk-components': 'warn',
|
|
44
49
|
'shop-minis/prefer-sdk-hooks': 'warn',
|
|
45
50
|
'shop-minis/validate-manifest': 'error',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable import/extensions */
|
|
2
2
|
/**
|
|
3
3
|
* ESLint rule wrapper for eslint-plugin-no-secrets with custom messages
|
|
4
|
-
*
|
|
4
|
+
* and URL-aware delimiter handling
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const noSecretsPlugin = require('eslint-plugin-no-secrets')
|
|
@@ -9,6 +9,21 @@ const noSecretsPlugin = require('eslint-plugin-no-secrets')
|
|
|
9
9
|
// Get the original rule
|
|
10
10
|
const originalRule = noSecretsPlugin.rules['no-secrets']
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Create a context-like object that overrides options while delegating
|
|
14
|
+
* all other property access to the original context
|
|
15
|
+
*/
|
|
16
|
+
function createContextWithOptions(context, newOptions) {
|
|
17
|
+
const wrapper = Object.create(context)
|
|
18
|
+
Object.defineProperty(wrapper, 'options', {
|
|
19
|
+
value: newOptions,
|
|
20
|
+
writable: false,
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
})
|
|
24
|
+
return wrapper
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
module.exports = {
|
|
13
28
|
meta: {
|
|
14
29
|
...originalRule.meta,
|
|
@@ -19,9 +34,36 @@ module.exports = {
|
|
|
19
34
|
'Potential {{ name }} detected. Hardcoded credentials should never be committed. Please check the Shop Minis documentation learn how to handle these cases. You can disable this rule if it is not a credential.',
|
|
20
35
|
},
|
|
21
36
|
},
|
|
37
|
+
|
|
22
38
|
create(context) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
const baseOptions = context.options[0] || {}
|
|
40
|
+
|
|
41
|
+
// Visitor for NON-URLs: no extra delimiters, skip URLs
|
|
42
|
+
const nonUrlContext = createContextWithOptions(context, [
|
|
43
|
+
{
|
|
44
|
+
...baseOptions,
|
|
45
|
+
ignoreContent: [...(baseOptions.ignoreContent || []), '^https?://'],
|
|
46
|
+
},
|
|
47
|
+
])
|
|
48
|
+
const nonUrlVisitor = originalRule.create(nonUrlContext)
|
|
49
|
+
|
|
50
|
+
// Visitor for URLs ONLY: with URL delimiters to reduce false positives
|
|
51
|
+
const urlContext = createContextWithOptions(context, [
|
|
52
|
+
{
|
|
53
|
+
...baseOptions,
|
|
54
|
+
ignoreContent: [...(baseOptions.ignoreContent || []), '^(?!https?://)'],
|
|
55
|
+
additionalDelimiters: ['/', '\\?', '=', '&', '\\+'],
|
|
56
|
+
},
|
|
57
|
+
])
|
|
58
|
+
const urlVisitor = originalRule.create(urlContext)
|
|
59
|
+
|
|
60
|
+
// Only process Literal nodes - skip TemplateElement to avoid false positives
|
|
61
|
+
// on template expressions like `${variable}` which get split incorrectly
|
|
62
|
+
return {
|
|
63
|
+
Literal(node) {
|
|
64
|
+
nonUrlVisitor.Literal?.(node)
|
|
65
|
+
urlVisitor.Literal?.(node)
|
|
66
|
+
},
|
|
67
|
+
}
|
|
26
68
|
},
|
|
27
69
|
}
|
|
@@ -221,32 +221,84 @@ module.exports = {
|
|
|
221
221
|
},
|
|
222
222
|
|
|
223
223
|
// Detect browser API usage for permissions
|
|
224
|
-
MemberExpression(
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
MemberExpression() {
|
|
225
|
+
// This visitor is kept for potential future permission detection
|
|
226
|
+
// getUserMedia detection has been moved to CallExpression for better constraint analysis
|
|
227
|
+
},
|
|
227
228
|
|
|
228
|
-
|
|
229
|
+
// Detect network requests and event listeners
|
|
230
|
+
CallExpression(node) {
|
|
231
|
+
// Check for getUserMedia calls
|
|
229
232
|
if (
|
|
230
|
-
|
|
231
|
-
|
|
233
|
+
node.callee.type === 'MemberExpression' &&
|
|
234
|
+
node.callee.property.name === 'getUserMedia'
|
|
232
235
|
) {
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
236
|
+
// Validate it's actually navigator.getUserMedia or navigator.mediaDevices.getUserMedia
|
|
237
|
+
const sourceCode = context.getSourceCode()
|
|
238
|
+
const calleeText = sourceCode.getText(node.callee)
|
|
239
|
+
|
|
240
|
+
if (
|
|
241
|
+
calleeText.includes('navigator.mediaDevices.getUserMedia') ||
|
|
242
|
+
calleeText.includes('navigator.getUserMedia')
|
|
243
|
+
) {
|
|
244
|
+
// Get the constraints argument (first argument)
|
|
245
|
+
const constraints = node.arguments[0]
|
|
246
|
+
|
|
247
|
+
let needsCamera = true
|
|
248
|
+
let needsMicrophone = true
|
|
249
|
+
|
|
250
|
+
if (constraints && constraints.type === 'ObjectExpression') {
|
|
251
|
+
// Check if video is explicitly set to false
|
|
252
|
+
const videoProp = constraints.properties.find(
|
|
253
|
+
prop =>
|
|
254
|
+
prop.type === 'Property' &&
|
|
255
|
+
prop.key &&
|
|
256
|
+
(prop.key.name === 'video' ||
|
|
257
|
+
(prop.key.type === 'Literal' && prop.key.value === 'video'))
|
|
258
|
+
)
|
|
259
|
+
if (
|
|
260
|
+
videoProp &&
|
|
261
|
+
videoProp.value.type === 'Literal' &&
|
|
262
|
+
videoProp.value.value === false
|
|
263
|
+
) {
|
|
264
|
+
needsCamera = false
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Check if audio is explicitly set to false
|
|
268
|
+
const audioProp = constraints.properties.find(
|
|
269
|
+
prop =>
|
|
270
|
+
prop.type === 'Property' &&
|
|
271
|
+
prop.key &&
|
|
272
|
+
(prop.key.name === 'audio' ||
|
|
273
|
+
(prop.key.type === 'Literal' && prop.key.value === 'audio'))
|
|
274
|
+
)
|
|
275
|
+
if (
|
|
276
|
+
audioProp &&
|
|
277
|
+
audioProp.value.type === 'Literal' &&
|
|
278
|
+
audioProp.value.value === false
|
|
279
|
+
) {
|
|
280
|
+
needsMicrophone = false
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (needsCamera) {
|
|
285
|
+
requiredPermissions.add({
|
|
286
|
+
permission: 'CAMERA',
|
|
287
|
+
reason: 'getUserMedia API usage with video',
|
|
288
|
+
node,
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (needsMicrophone) {
|
|
293
|
+
requiredPermissions.add({
|
|
294
|
+
permission: 'MICROPHONE',
|
|
295
|
+
reason: 'getUserMedia API usage with audio',
|
|
296
|
+
node,
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
}
|
|
245
300
|
}
|
|
246
|
-
},
|
|
247
301
|
|
|
248
|
-
// Detect network requests and event listeners
|
|
249
|
-
CallExpression(node) {
|
|
250
302
|
if (!node.arguments[0]) {
|
|
251
303
|
return
|
|
252
304
|
}
|