Skip to content
Back to skills

1228 Patterns 1a18c198

ASecurity

Common workflows, full-stack flows, and best practices.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
developmenttypescriptgoreactapifrontendbackend

Works with

  • cli
  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 1228-patterns_1a18c198 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 1228 Patterns 1a18c198?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 1228 Patterns 1a18c198
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-1228-patterns-1a18c198/badge)](https://www.skillsdirectory.com/skills/tools-only-1228-patterns-1a18c198)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Stream Patterns

Common workflows, full-stack flows, and best practices.

## React Stream Player

`npm install @cloudflare/stream-react`

```tsx
import { Stream } from '@cloudflare/stream-react';

export function VideoPlayer({ videoId, token }: { videoId: string; token?: string }) {
  return <Stream controls src={token ? `${videoId}?token=${token}` : videoId} responsive />;
}
```

## Full-Stack Upload Flow

**Backend API (Workers/Pages)**
```typescript
import Cloudflare from 'cloudflare';

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { videoName } = await request.json();
    const client = new Cloudflare({ apiToken: env.CF_API_TOKEN });
    const { uploadURL, uid } = await client.stream.directUpload.create({
      account_id: env.CF_ACCOUNT_ID,
      maxDurationSeconds: 3600,
      requireSignedURLs: true,
      meta: { name: videoName }
    });
    return Response.json({ uploadURL, uid });
  }
};
```

**Frontend component**
```tsx
import { useState } from 'react';

export function VideoUploader() {
  const [uploading, setUploading] = useState(false);
  const [progress, setProgress] = useState(0);
  
  async function handleUpload(file: File) {
    setUploading(true);
    const { uploadURL, uid } = await fetch('/api/upload-url', {
      method: 'POST',
      body: JSON.stringify({ videoName: file.name })
    }).then(r => r.json());
    
    const xhr = new XMLHttpRequest();
    xhr.upload.onprogress = (e) => setProgress((e.loaded / e.total) * 100);
    xhr.onload = () => { setUploading(false); window.location.href = `/videos/${uid}`; };
    xhr.open('POST', uploadURL);
    const formData = new FormData();
    formData.append('file', file);
    xhr.send(formData);
  }
  
  return (
    <div>
      <input type="file" accept="video/*" onChange={(e) => e.target.files?.[0] && handleUpload(e.target.files[0])} disabled={uploading} />
      {uploading && <progress value={progress} max={100} />}
    </div>
  );
}
```

## TUS Resumable Upload

For large files (>500MB). `npm install tus-js-client`

```typescript
import * as tus from 'tus-js-client';

async function uploadWithTUS(file: File, uploadURL: string, onProgress?: (pct: number) => void) {
  return new Promise<string>((resolve, reject) => {
    const upload = new tus.Upload(file, {
      endpoint: uploadURL,
      retryDelays: [0, 3000, 5000, 10000, 20000],
      chunkSize: 50 * 1024 * 1024,
      metadata: { filename: file.name, filetype: file.type },
      onError: reject,
      onProgress: (up, total) => onProgress?.((up / total) * 100),
      onSuccess: () => resolve(upload.url?.split('/').pop() || '')
    });
    upload.start();
  });
}
```

## Video State Polling

```typescript
async function waitForVideoReady(client: Cloudflare, accountId: string, videoId: string) {
  for (let i = 0; i < 60; i++) {
    const video = await client.stream.videos.get(videoId, { account_id: accountId });
    if (video.readyToStream || video.status.state === 'error') return video;
    await new Promise(resolve => setTimeout(resolve, 5000));
  }
  throw new Error('Video processing timeout');
}
```

## Webhook Handler

```typescript
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const signature = request.headers.get('Webhook-Signature');
    const body = await request.text();
    if (!signature || !await verifyWebhook(signature, body, env.WEBHOOK_SECRET)) {
      return new Response('Unauthorized', { status: 401 });
    }
    const payload = JSON.parse(body);
    if (payload.readyToStream) console.log(`Video ${payload.uid} ready`);
    return new Response('OK');
  }
};

async function verifyWebhook(sig: string, body: string, secret: string): Promise<boolean> {
  const parts = Object.fromEntries(sig.split(',').map(p => p.split('=')));
  const timestamp = parseInt(parts.time || '0', 10);
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
  
  const key = await crypto.subtle.importKey(
    'raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
  );
  const computed = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(`${timestamp}.${body}`));
  const hex = Array.from(new Uint8Array(computed), b => b.toString(16).padStart(2, '0')).join('');
  return hex === parts.sig1;
}
```

## Self-Sign JWT (High Volume Tokens)

For >1k tokens/day. Prerequisites: Create signing key (see configuration.md).

```typescript
async function selfSignToken(keyId: string, jwkBase64: string, videoId: string, expiresIn = 3600) {
  const key = await crypto.subtle.importKey(
    'jwk', JSON.parse(atob(jwkBase64)), { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['sign']
  );
  const now = Math.floor(Date.now() / 1000);
  const header = btoa(JSON.stringify({ alg: 'RS256', kid: keyId })).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
  const payload = btoa(JSON.stringify({ sub: videoId, kid: keyId, exp: now + expiresIn, nbf: now }))
    .replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
  const message = `${header}.${payload}`;
  const sig = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', key, new TextEncoder().encode(message));
  const b64Sig = btoa(String.fromCharCode(...new Uint8Array(sig))).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
  return `${message}.${b64Sig}`;
}

// With access rules (geo-restriction)
const payloadWithRules = {
  sub: videoId, kid: keyId, exp: now + 3600, nbf: now,
  accessRules: [{ type: 'ip.geoip.country', action: 'allow', country: ['US'] }]
};
```

## Best Practices

- **Use Direct Creator Uploads** - Avoid proxying through servers
- **Enable requireSignedURLs** - Control private content access
- **Self-sign tokens at scale** - Use signing keys for >1k/day
- **Set allowedOrigins** - Prevent hotlinking
- **Use webhooks over polling** - Efficient status updates
- **Set maxDurationSeconds** - Prevent abuse
- **Enable live recordings** - Auto VOD after stream

## In This Reference

- [README.md](./README.md) - Overview and quick start
- [configuration.md](./configuration.md) - Setup and config
- [api.md](./api.md) - On-demand video APIs
- [api-live.md](./api-live.md) - Live streaming APIs
- [gotchas.md](./gotchas.md) - Error codes, troubleshooting

## See Also

- [workers](../workers/) - Deploy Stream APIs in Workers
- [pages](../pages/) - Integrate Stream with Pages

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.