# 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!
