@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': 'error',
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
- * @fileoverview Customized messages for detecting hardcoded secrets in Shop Minis
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
- // Create the original rule's visitor
24
- const originalVisitor = originalRule.create(context)
25
- return originalVisitor
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(node) {
225
- const sourceCode = context.getSourceCode()
226
- const code = sourceCode.getText(node)
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
- // Check for camera/microphone API
229
+ // Detect network requests and event listeners
230
+ CallExpression(node) {
231
+ // Check for getUserMedia calls
229
232
  if (
230
- code.includes('navigator.mediaDevices.getUserMedia') ||
231
- code.includes('navigator.getUserMedia')
233
+ node.callee.type === 'MemberExpression' &&
234
+ node.callee.property.name === 'getUserMedia'
232
235
  ) {
233
- // Need to check the constraints to determine if it's camera or microphone
234
- // For now, we'll flag both as potentially needed
235
- requiredPermissions.add({
236
- permission: 'CAMERA',
237
- reason: 'getUserMedia API usage',
238
- node,
239
- })
240
- requiredPermissions.add({
241
- permission: 'MICROPHONE',
242
- reason: 'getUserMedia API usage',
243
- node,
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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shopify/shop-minis-react",
3
3
  "license": "SEE LICENSE IN LICENSE.txt",
4
- "version": "0.4.5",
4
+ "version": "0.4.7",
5
5
  "sideEffects": false,
6
6
  "type": "module",
7
7
  "engines": {