UNPKG

@shopify/shop-minis-react

Version:

React component library for Shopify Shop Minis with Tailwind CSS v4 support (source-only, requires TypeScript)

311 lines (228 loc) 8.11 kB
# Shop Minis ESLint Plugin Custom ESLint rules for Shop Minis apps. ESLint is included with the SDK. ## Quick Setup Create **`eslint.config.js`** (NOT `.eslintrc.js`): ```javascript const shopMinisConfig = require('@shopify/shop-minis-react/eslint/config') module.exports = [shopMinisConfig] ``` **That's it!** TypeScript and JSX are supported out of the box. **Important:** - File must be named `eslint.config.js` (no dot, no "rc") - This will lint all `.js`, `.jsx`, `.ts`, `.tsx` files in your project - TypeScript and JSX parsing is configured automatically ## Usage ```bash # Check for errors npx eslint . # Auto-fix (converts <img> to <Image> AND adds import!) npx eslint . --fix ``` ## What You Get ### Custom Shop Minis Rules - ✅ No internal imports allowed - ✅ Warnings for `<img>`, `<button>`, `<label>` tags (auto-fixes with imports) - ✅ Manifest scope validation - ensures manifest.json has scopes for hooks you use (auto-fixes manifest) ### Security Rules (Using Built-in ESLint Rules) - ✅ WebAssembly usage blocked - prevents WASM in Shop Minis environment - ✅ Unsafe code execution blocked - prevents `eval()`, Function constructor, and dynamic code execution -`dangerouslySetInnerHTML` blocked - prevents XSS vulnerabilities -`window.open` blocked - use SDK navigation instead - ✅ Navigator APIs blocked - `clipboard`, `credentials`, `geolocation`, `share` are not available ## Rules ### `no-internal-imports` Prevents importing from internal SDK directories. ```tsx // ❌ Error import {something} from '@shopify/shop-minis-react/internal' // ✅ Correct import {Component} from '@shopify/shop-minis-react' ``` ### `prefer-sdk-components` Suggests using SDK components instead of native HTML elements. **Fully auto-fixable** - fixes both tags and imports! **Before:** ```tsx const MyComponent = () => ( <img src="product.jpg" alt="Product" /> ) ``` **After running `npx eslint . --fix`:** ```tsx import {Image} from '@shopify/shop-minis-react' const MyComponent = () => ( <Image src="product.jpg" alt="Product" /> ) ``` **Supported Components:** - `<img>``<Image>` - `<button>``<Button>` - `<label>``<Label>` **Auto-fix does TWO things:** 1. ✅ Replaces native element with SDK component 2. ✅ Adds import statement automatically (or adds to existing import) ### `validate-manifest` Validates `src/manifest.json` configuration for scopes and permissions. **Auto-fixable** - adds missing values to manifest! #### Scopes Checks that hooks have required scopes: ```tsx // If you use this hook: import {useCurrentUser} from '@shopify/shop-minis-react' // Manifest must include: { "scopes": ["USER_SETTINGS_READ"] } ``` **Scope Requirements:** - `useCurrentUser``USER_SETTINGS_READ` - `useSavedProducts``FAVORITES` - `useOrders``ORDERS` #### Permissions Checks for native permission usage: ```tsx // If you use this hook: import {useImagePicker} from '@shopify/shop-minis-react' // Or browser APIs: navigator.mediaDevices.getUserMedia({video: true}) // Manifest must include: { "permissions": ["CAMERA"] } ``` **Supported Permissions:** - `CAMERA` - Required for `useImagePicker` hook or getUserMedia video - `MICROPHONE` - Required for getUserMedia audio - `MOTION` - Required for DeviceOrientation/DeviceMotion events #### Trusted Domains Checks that external URLs are in trusted_domains. Detects: **Network Requests:** - `fetch('https://api.example.com/data')` - `new XMLHttpRequest().open('GET', 'https://...')` - `new WebSocket('wss://api.example.com')` - `new EventSource('https://api.example.com/events')` - `navigator.sendBeacon('https://analytics.example.com')` - `window.open('https://external.com')` **Media & Resources:** - `<img src="https://cdn.shopify.com/image.jpg" />` - `<video src="https://videos.example.com/video.mp4" />` - `<video poster="https://cdn.example.com/poster.jpg" />` - `<audio src="https://audio.example.com/sound.mp3" />` - `<source src="https://media.example.com/video.mp4" />` - `<track src="https://cdn.example.com/captions.vtt" />` - `<object data="https://cdn.example.com/file.pdf" />` - `<embed src="https://cdn.example.com/file.swf" />` - `<form action="https://api.example.com/submit" />` **Note:** External scripts (`<script>`), stylesheets (`<link>`), and iframes are not supported and excluded from validation. **Example manifest:** ```json { "trusted_domains": [ "api.example.com", "cdn.shopify.com", "videos.example.com" ] } ``` **Wildcard support:** ```json { "trusted_domains": [ "*.shopify.com", // Matches any Shopify subdomain "api.example.com", // Exact domain "cdn.example.com/assets" // Specific path ] } ``` **Auto-fix:** ```bash npx eslint . --fix # Automatically updates src/manifest.json ``` **Example errors:** ``` Hook "useCurrentUser" requires scope "USER_SETTINGS_READ" in src/manifest.json. Hook "useImagePicker" requires permission "CAMERA" in src/manifest.json. fetch() call loads from "api.example.com" which is not in trusted_domains. <img> src attribute loads from "cdn.shopify.com" which is not in trusted_domains. ``` ## Security Rules The following security restrictions are enforced using standard ESLint plugins and built-in rules: ### WebAssembly Restrictions **Rules:** `no-restricted-globals`, `no-restricted-syntax` ```tsx // ❌ Error const module = new WebAssembly.Module(bytes) WebAssembly.instantiate(bytes) WebAssembly.compile(bytes) // ✅ Correct // Use JavaScript implementations instead of WebAssembly ``` **Why:** WebAssembly is not supported in the Shop Minis security sandbox. ### Unsafe Code Execution **Rules:** `no-eval`, `no-implied-eval`, `no-new-func`, `no-script-url` (built-in ESLint rules) ```tsx // ❌ Error eval('alert(1)') new Function('a', 'b', 'return a + b') setTimeout('alert(1)', 1000) window.location = 'javascript:void(0)' // ✅ Correct const add = (a, b) => a + b setTimeout(() => alert(1), 1000) window.location = 'https://example.com' ``` **Why:** Dynamic code execution can bypass security restrictions. **Built-in rules used:** - `no-eval` - blocks `eval()` and `window.eval()` - `no-new-func` - blocks `new Function()` constructor - `no-implied-eval` - blocks `setTimeout()` / `setInterval()` with string arguments - `no-script-url` - blocks `javascript:` URLs ### Dangerous HTML Injection **Rule:** `react/no-danger` ```tsx // ❌ Error <div dangerouslySetInnerHTML={{__html: userInput}} /> // ✅ Correct <div>{sanitizedContent}</div> ``` **Why:** Injecting raw HTML can lead to XSS (Cross-Site Scripting) attacks. ### Window Open Restriction **Rule:** `no-restricted-syntax` ```tsx // ❌ Error window.open('https://example.com', '_blank') // ✅ Correct // Use SDK navigation methods instead import {useNavigation} from '@shopify/shop-minis-react' const {navigate} = useNavigation() navigate('https://example.com') ``` **Why:** `window.open` is not allowed in the Shop Minis environment. Use SDK navigation methods instead. ### Navigator API Restrictions **Rule:** `no-restricted-syntax` The following Navigator APIs are not available in the Shop Minis environment: ```tsx // ❌ Error - Clipboard API navigator.clipboard.writeText('text') navigator.clipboard.readText() // ❌ Error - Credentials API navigator.credentials.get({password: true}) navigator.credentials.store(credential) // ❌ Error - Geolocation API navigator.geolocation.getCurrentPosition(callback) navigator.geolocation.watchPosition(callback) // ❌ Error - Share API navigator.share({title: 'Title', url: 'https://...'}) ``` **Why:** These browser APIs are not supported in the Shop Minis security sandbox. Use the appropriate SDK alternatives when available. ## Extending Rules To add more component mappings to `prefer-sdk-components`, edit `eslint/rules/prefer-sdk-components.cjs`: ```javascript const defaultComponents = { img: 'Image', button: 'Button', label: 'Label', input: 'Input', // Add this a: 'TransitionLink', // Add this } ``` All consumers automatically get new rules - no config changes needed!