@girardmedia/bootspring 3.3.2 → 3.4.0
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/assets/agents/accessibility-auditor.md +39 -0
- package/assets/agents/api-designer.md +40 -0
- package/assets/agents/auth-implementer.md +64 -0
- package/assets/agents/bug-hunter.md +42 -0
- package/assets/agents/bundle-analyzer.md +40 -0
- package/assets/agents/cache-optimizer.md +55 -0
- package/assets/agents/changelog-writer.md +55 -0
- package/assets/agents/ci-cd-builder.md +40 -0
- package/assets/agents/code-explainer.md +39 -0
- package/assets/agents/code-reviewer.md +39 -0
- package/assets/agents/cost-optimizer.md +57 -0
- package/assets/agents/cron-scheduler.md +51 -0
- package/assets/agents/data-seeder.md +56 -0
- package/assets/agents/database-architect.md +40 -0
- package/assets/agents/dependency-updater.md +40 -0
- package/assets/agents/deploy-checker.md +40 -0
- package/assets/agents/docker-optimizer.md +40 -0
- package/assets/agents/documentation-writer.md +40 -0
- package/assets/agents/email-builder.md +55 -0
- package/assets/agents/env-setup.md +40 -0
- package/assets/agents/error-handler.md +40 -0
- package/assets/agents/eslint-fixer.md +46 -0
- package/assets/agents/feature-flagger.md +69 -0
- package/assets/agents/git-detective.md +39 -0
- package/assets/agents/graphql-builder.md +60 -0
- package/assets/agents/incident-responder.md +59 -0
- package/assets/agents/log-analyzer.md +39 -0
- package/assets/agents/migration-planner.md +41 -0
- package/assets/agents/monorepo-navigator.md +39 -0
- package/assets/agents/nextjs-expert.md +57 -0
- package/assets/agents/notification-builder.md +56 -0
- package/assets/agents/onboarding-guide.md +39 -0
- package/assets/agents/performance-profiler.md +40 -0
- package/assets/agents/prisma-expert.md +57 -0
- package/assets/agents/rate-limiter.md +58 -0
- package/assets/agents/react-expert.md +58 -0
- package/assets/agents/refactorer.md +42 -0
- package/assets/agents/regex-builder.md +46 -0
- package/assets/agents/release-manager.md +40 -0
- package/assets/agents/s3-manager.md +58 -0
- package/assets/agents/schema-validator.md +40 -0
- package/assets/agents/search-builder.md +62 -0
- package/assets/agents/security-auditor.md +39 -0
- package/assets/agents/sitemap-generator.md +53 -0
- package/assets/agents/stripe-integrator.md +59 -0
- package/assets/agents/tailwind-expert.md +55 -0
- package/assets/agents/tech-debt-tracker.md +39 -0
- package/assets/agents/test-writer.md +42 -0
- package/assets/agents/type-fixer.md +45 -0
- package/assets/agents/webhook-builder.md +54 -0
- package/assets/rules/cpp.md +53 -0
- package/assets/rules/css.md +52 -0
- package/assets/rules/go.md +50 -0
- package/assets/rules/html.md +52 -0
- package/assets/rules/java.md +51 -0
- package/assets/rules/kotlin.md +50 -0
- package/assets/rules/php.md +51 -0
- package/assets/rules/python.md +51 -0
- package/assets/rules/ruby.md +51 -0
- package/assets/rules/rust.md +49 -0
- package/assets/rules/shell.md +52 -0
- package/assets/rules/sql.md +49 -0
- package/assets/rules/swift.md +50 -0
- package/assets/rules/typescript.md +52 -0
- package/assets/rules/yaml-json.md +51 -0
- package/assets/skills/accessibility.md +210 -0
- package/assets/skills/agent-patterns.md +387 -0
- package/assets/skills/ai-integration.md +263 -0
- package/assets/skills/animation-patterns.md +224 -0
- package/assets/skills/api-design.md +218 -0
- package/assets/skills/api-gateway.md +341 -0
- package/assets/skills/api-versioning.md +226 -0
- package/assets/skills/astro-patterns.md +233 -0
- package/assets/skills/auth-patterns.md +248 -0
- package/assets/skills/aws-patterns.md +171 -0
- package/assets/skills/background-jobs.md +162 -0
- package/assets/skills/browser-extensions.md +309 -0
- package/assets/skills/caching-patterns.md +253 -0
- package/assets/skills/ci-cd.md +251 -0
- package/assets/skills/cli-development.md +296 -0
- package/assets/skills/code-review.md +185 -0
- package/assets/skills/cron-patterns.md +327 -0
- package/assets/skills/data-fetching.md +231 -0
- package/assets/skills/database-migrations.md +346 -0
- package/assets/skills/database-patterns.md +219 -0
- package/assets/skills/debugging.md +281 -0
- package/assets/skills/design-system.md +289 -0
- package/assets/skills/django-patterns.md +182 -0
- package/assets/skills/docker-patterns.md +235 -0
- package/assets/skills/e2e-testing.md +287 -0
- package/assets/skills/edge-computing.md +268 -0
- package/assets/skills/electron-patterns.md +266 -0
- package/assets/skills/email-templates.md +206 -0
- package/assets/skills/error-handling.md +265 -0
- package/assets/skills/event-driven.md +232 -0
- package/assets/skills/express-patterns.md +239 -0
- package/assets/skills/fastapi-patterns.md +198 -0
- package/assets/skills/feature-flags.md +212 -0
- package/assets/skills/figma-to-code.md +298 -0
- package/assets/skills/file-upload.md +228 -0
- package/assets/skills/forms-patterns.md +264 -0
- package/assets/skills/gcp-patterns.md +189 -0
- package/assets/skills/git-workflow.md +187 -0
- package/assets/skills/golang-patterns.md +185 -0
- package/assets/skills/graphql-patterns.md +244 -0
- package/assets/skills/i18n-patterns.md +172 -0
- package/assets/skills/image-processing.md +350 -0
- package/assets/skills/java-springboot.md +226 -0
- package/assets/skills/kotlin-patterns.md +207 -0
- package/assets/skills/kubernetes-patterns.md +326 -0
- package/assets/skills/laravel-patterns.md +261 -0
- package/assets/skills/llm-fine-tuning.md +335 -0
- package/assets/skills/load-testing.md +303 -0
- package/assets/skills/logging-observability.md +228 -0
- package/assets/skills/markdown-processing.md +318 -0
- package/assets/skills/mcp-server-patterns.md +292 -0
- package/assets/skills/microservices.md +272 -0
- package/assets/skills/migration-patterns.md +239 -0
- package/assets/skills/mongodb-patterns.md +189 -0
- package/assets/skills/monorepo-patterns.md +287 -0
- package/assets/skills/nextjs-app-router.md +237 -0
- package/assets/skills/notification-patterns.md +348 -0
- package/assets/skills/oauth-patterns.md +246 -0
- package/assets/skills/payment-integration.md +222 -0
- package/assets/skills/pdf-generation.md +307 -0
- package/assets/skills/performance-optimization.md +277 -0
- package/assets/skills/php-patterns.md +210 -0
- package/assets/skills/prisma-patterns.md +241 -0
- package/assets/skills/prompt-engineering.md +193 -0
- package/assets/skills/pwa-patterns.md +247 -0
- package/assets/skills/python-patterns.md +158 -0
- package/assets/skills/python-testing.md +172 -0
- package/assets/skills/queue-patterns.md +295 -0
- package/assets/skills/rag-patterns.md +159 -0
- package/assets/skills/rate-limiting.md +319 -0
- package/assets/skills/react-components.md +201 -0
- package/assets/skills/react-native-patterns.md +299 -0
- package/assets/skills/real-time-patterns.md +181 -0
- package/assets/skills/redis-patterns.md +188 -0
- package/assets/skills/refactoring.md +218 -0
- package/assets/skills/regex-patterns.md +191 -0
- package/assets/skills/remix-patterns.md +262 -0
- package/assets/skills/responsive-design.md +199 -0
- package/assets/skills/ruby-rails-patterns.md +178 -0
- package/assets/skills/rust-patterns.md +211 -0
- package/assets/skills/search-patterns.md +227 -0
- package/assets/skills/security-hardening.md +237 -0
- package/assets/skills/seo-patterns.md +179 -0
- package/assets/skills/serverless-patterns.md +223 -0
- package/assets/skills/sql-optimization.md +154 -0
- package/assets/skills/state-management.md +254 -0
- package/assets/skills/storybook-patterns.md +330 -0
- package/assets/skills/svelte-patterns.md +258 -0
- package/assets/skills/swift-patterns.md +227 -0
- package/assets/skills/tailwind-patterns.md +272 -0
- package/assets/skills/tdd-workflow.md +199 -0
- package/assets/skills/terraform-patterns.md +270 -0
- package/assets/skills/testing-react.md +240 -0
- package/assets/skills/testing-vitest.md +232 -0
- package/assets/skills/typescript-strict.md +159 -0
- package/assets/skills/video-processing.md +340 -0
- package/assets/skills/vue-patterns.md +247 -0
- package/assets/skills/web-workers.md +327 -0
- package/assets/skills/webhooks-patterns.md +283 -0
- package/assets/skills/websocket-patterns.md +306 -0
- package/dist/cli/index.js +941 -958
- package/dist/core/index.d.ts +341 -11
- package/dist/core.js +58 -95
- package/dist/mcp/index.d.ts +33 -1
- package/dist/mcp-server.js +177 -255
- package/package.json +4 -1
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: edge-computing
|
|
3
|
+
description: Edge computing patterns for Cloudflare Workers, edge middleware, geolocation routing, KV storage, and Durable Objects.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Edge Computing Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
Deploy logic to the edge when you need sub-50ms response times globally, geolocation-based routing, request transformation before reaching origin servers, or lightweight compute that does not require a full server. Edge functions are ideal for A/B testing, auth token validation, redirects, rate limiting, and personalization. Avoid edge for CPU-heavy workloads or anything requiring persistent connections to a single database.
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
### Cloudflare Worker Basics
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// src/worker.ts
|
|
17
|
+
export interface Env {
|
|
18
|
+
MY_KV: KVNamespace;
|
|
19
|
+
MY_R2: R2Bucket;
|
|
20
|
+
API_KEY: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
25
|
+
const url = new URL(request.url);
|
|
26
|
+
|
|
27
|
+
// Route to handlers
|
|
28
|
+
if (url.pathname.startsWith('/api/')) return handleAPI(request, env, ctx);
|
|
29
|
+
if (url.pathname === '/health') return new Response('ok', { status: 200 });
|
|
30
|
+
|
|
31
|
+
return new Response('Not found', { status: 404 });
|
|
32
|
+
},
|
|
33
|
+
} satisfies ExportedHandler<Env>;
|
|
34
|
+
|
|
35
|
+
async function handleAPI(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
36
|
+
// Verify API key
|
|
37
|
+
const apiKey = request.headers.get('X-API-Key');
|
|
38
|
+
if (apiKey !== env.API_KEY) {
|
|
39
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const data = await env.MY_KV.get('cache:data', 'json');
|
|
43
|
+
return Response.json(data ?? { items: [] });
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Edge Middleware (Next.js / Vercel)
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// middleware.ts (project root)
|
|
51
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
52
|
+
|
|
53
|
+
export function middleware(request: NextRequest) {
|
|
54
|
+
const { pathname, searchParams } = request.nextUrl;
|
|
55
|
+
const country = request.geo?.country ?? 'US';
|
|
56
|
+
const city = request.geo?.city ?? 'Unknown';
|
|
57
|
+
|
|
58
|
+
// Geolocation-based redirect
|
|
59
|
+
if (pathname === '/' && country === 'DE') {
|
|
60
|
+
return NextResponse.redirect(new URL('/de', request.url));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// A/B testing via cookie
|
|
64
|
+
const bucket = request.cookies.get('ab-bucket')?.value;
|
|
65
|
+
if (!bucket && pathname === '/pricing') {
|
|
66
|
+
const assigned = Math.random() < 0.5 ? 'control' : 'variant';
|
|
67
|
+
const response = NextResponse.rewrite(
|
|
68
|
+
new URL(`/pricing/${assigned}`, request.url)
|
|
69
|
+
);
|
|
70
|
+
response.cookies.set('ab-bucket', assigned, { maxAge: 60 * 60 * 24 * 30 });
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add custom headers
|
|
75
|
+
const response = NextResponse.next();
|
|
76
|
+
response.headers.set('X-Country', country);
|
|
77
|
+
response.headers.set('X-City', city);
|
|
78
|
+
return response;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const config = {
|
|
82
|
+
matcher: ['/', '/pricing/:path*', '/api/:path*'],
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### KV Storage Patterns
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// src/kv-cache.ts
|
|
90
|
+
export async function cachedFetch(
|
|
91
|
+
kv: KVNamespace,
|
|
92
|
+
cacheKey: string,
|
|
93
|
+
fetcher: () => Promise<unknown>,
|
|
94
|
+
ttlSeconds: number = 300
|
|
95
|
+
): Promise<unknown> {
|
|
96
|
+
// Try KV cache first
|
|
97
|
+
const cached = await kv.get(cacheKey, 'json');
|
|
98
|
+
if (cached !== null) return cached;
|
|
99
|
+
|
|
100
|
+
// Fetch from origin
|
|
101
|
+
const data = await fetcher();
|
|
102
|
+
|
|
103
|
+
// Store in KV with TTL (non-blocking)
|
|
104
|
+
await kv.put(cacheKey, JSON.stringify(data), {
|
|
105
|
+
expirationTtl: ttlSeconds,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return data;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// KV with metadata for cache management
|
|
112
|
+
export async function putWithMetadata(
|
|
113
|
+
kv: KVNamespace,
|
|
114
|
+
key: string,
|
|
115
|
+
value: unknown,
|
|
116
|
+
metadata: { version: number; source: string }
|
|
117
|
+
) {
|
|
118
|
+
await kv.put(key, JSON.stringify(value), {
|
|
119
|
+
expirationTtl: 3600,
|
|
120
|
+
metadata,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function getWithMetadata(kv: KVNamespace, key: string) {
|
|
125
|
+
const { value, metadata } = await kv.getWithMetadata<{ version: number; source: string }>(
|
|
126
|
+
key, 'json'
|
|
127
|
+
);
|
|
128
|
+
return { data: value, metadata };
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Durable Objects (Stateful Edge)
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// src/rate-limiter.ts
|
|
136
|
+
export class RateLimiter implements DurableObject {
|
|
137
|
+
private requests: number[] = [];
|
|
138
|
+
private readonly limit = 100;
|
|
139
|
+
private readonly windowMs = 60_000;
|
|
140
|
+
|
|
141
|
+
constructor(private state: DurableObjectState, private env: Env) {}
|
|
142
|
+
|
|
143
|
+
async fetch(request: Request): Promise<Response> {
|
|
144
|
+
const now = Date.now();
|
|
145
|
+
|
|
146
|
+
// Load state
|
|
147
|
+
this.requests = (await this.state.storage.get<number[]>('requests')) ?? [];
|
|
148
|
+
|
|
149
|
+
// Prune old entries
|
|
150
|
+
this.requests = this.requests.filter((t) => now - t < this.windowMs);
|
|
151
|
+
|
|
152
|
+
if (this.requests.length >= this.limit) {
|
|
153
|
+
const retryAfter = Math.ceil((this.requests[0] + this.windowMs - now) / 1000);
|
|
154
|
+
return new Response('Rate limit exceeded', {
|
|
155
|
+
status: 429,
|
|
156
|
+
headers: {
|
|
157
|
+
'Retry-After': String(retryAfter),
|
|
158
|
+
'X-RateLimit-Limit': String(this.limit),
|
|
159
|
+
'X-RateLimit-Remaining': '0',
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.requests.push(now);
|
|
165
|
+
await this.state.storage.put('requests', this.requests);
|
|
166
|
+
|
|
167
|
+
return Response.json({
|
|
168
|
+
remaining: this.limit - this.requests.length,
|
|
169
|
+
limit: this.limit,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Worker entry — route to Durable Object
|
|
175
|
+
export default {
|
|
176
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
177
|
+
const ip = request.headers.get('CF-Connecting-IP') ?? 'unknown';
|
|
178
|
+
const id = env.RATE_LIMITER.idFromName(ip);
|
|
179
|
+
const stub = env.RATE_LIMITER.get(id);
|
|
180
|
+
return stub.fetch(request);
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### R2 Object Storage at the Edge
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// src/r2-handler.ts
|
|
189
|
+
export async function handleR2(request: Request, env: Env): Promise<Response> {
|
|
190
|
+
const url = new URL(request.url);
|
|
191
|
+
const key = url.pathname.slice(1); // remove leading /
|
|
192
|
+
|
|
193
|
+
if (request.method === 'GET') {
|
|
194
|
+
const object = await env.MY_R2.get(key);
|
|
195
|
+
if (!object) return new Response('Not found', { status: 404 });
|
|
196
|
+
|
|
197
|
+
return new Response(object.body, {
|
|
198
|
+
headers: {
|
|
199
|
+
'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream',
|
|
200
|
+
'ETag': object.etag,
|
|
201
|
+
'Cache-Control': 'public, max-age=31536000, immutable',
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (request.method === 'PUT') {
|
|
207
|
+
const contentType = request.headers.get('Content-Type') ?? 'application/octet-stream';
|
|
208
|
+
await env.MY_R2.put(key, request.body, {
|
|
209
|
+
httpMetadata: { contentType },
|
|
210
|
+
});
|
|
211
|
+
return new Response('Created', { status: 201 });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return new Response('Method not allowed', { status: 405 });
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Geolocation-Based Content
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// src/geo-router.ts
|
|
222
|
+
interface GeoConfig {
|
|
223
|
+
country: string;
|
|
224
|
+
redirects: Record<string, string>;
|
|
225
|
+
blockedCountries: string[];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function geoRoute(request: Request, cf: IncomingRequestCfProperties): Response | null {
|
|
229
|
+
const country = cf.country ?? 'US';
|
|
230
|
+
const continent = cf.continent ?? 'NA';
|
|
231
|
+
|
|
232
|
+
// Block sanctioned regions
|
|
233
|
+
const blocked = ['KP', 'IR', 'SY'];
|
|
234
|
+
if (blocked.includes(country)) {
|
|
235
|
+
return new Response('Service unavailable in your region', { status: 451 });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Inject geolocation headers for downstream
|
|
239
|
+
const headers = new Headers();
|
|
240
|
+
headers.set('X-Geo-Country', country);
|
|
241
|
+
headers.set('X-Geo-Continent', continent);
|
|
242
|
+
headers.set('X-Geo-City', cf.city ?? 'Unknown');
|
|
243
|
+
headers.set('X-Geo-Latitude', String(cf.latitude ?? 0));
|
|
244
|
+
headers.set('X-Geo-Longitude', String(cf.longitude ?? 0));
|
|
245
|
+
|
|
246
|
+
return null; // continue to origin with headers
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Examples
|
|
251
|
+
|
|
252
|
+
| Pattern | Latency | State | Use Case |
|
|
253
|
+
|---------|---------|-------|----------|
|
|
254
|
+
| KV cache | ~10ms global | Eventually consistent | Config, feature flags, cached API |
|
|
255
|
+
| Durable Object | ~20ms | Strongly consistent | Rate limiting, counters, sessions |
|
|
256
|
+
| R2 storage | ~50ms | Strongly consistent | File uploads, static assets |
|
|
257
|
+
| Edge middleware | ~1ms overhead | Stateless | Auth, redirects, headers |
|
|
258
|
+
| Worker fetch | ~5ms cold start | Stateless | API proxy, transformation |
|
|
259
|
+
|
|
260
|
+
## Checklist
|
|
261
|
+
- [ ] Workers handle errors gracefully and return proper HTTP status codes
|
|
262
|
+
- [ ] KV keys include version or TTL metadata for cache invalidation
|
|
263
|
+
- [ ] Durable Objects used only when strong consistency is required
|
|
264
|
+
- [ ] Geolocation checks use `request.cf` (Cloudflare) or `request.geo` (Vercel)
|
|
265
|
+
- [ ] R2 responses include Cache-Control and ETag headers
|
|
266
|
+
- [ ] Sensitive logic (API keys, auth) runs at the edge, not in client
|
|
267
|
+
- [ ] Worker bundle size stays under 1MB (compressed) for fast cold starts
|
|
268
|
+
- [ ] `waitUntil()` used for non-blocking background work
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: electron-patterns
|
|
3
|
+
description: Build Electron apps — IPC communication, preload scripts, auto-update, window management, security, and packaging.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Electron Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Use Electron when building cross-platform desktop applications with web
|
|
11
|
+
technologies. Covers the critical security boundary between main and renderer
|
|
12
|
+
processes, IPC communication, preload scripts for safe API exposure,
|
|
13
|
+
auto-updates, window management, and packaging for distribution. Apply these
|
|
14
|
+
patterns from the start — Electron security mistakes are hard to retrofit.
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
### 1. Process Architecture
|
|
19
|
+
|
|
20
|
+
Electron has two process types. Never bypass this boundary.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Main Process (Node.js) Renderer Process (Chromium)
|
|
24
|
+
|- File system access |- UI rendering
|
|
25
|
+
|- Native APIs |- DOM manipulation
|
|
26
|
+
|- Window management |- Web APIs only
|
|
27
|
+
|- System tray +- No Node.js (when secure)
|
|
28
|
+
+- IPC handler
|
|
29
|
+
<-> IPC bridge (preload.ts)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. Preload Script — Safe API Surface
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// preload.ts — runs in renderer context with Node.js access
|
|
36
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
37
|
+
|
|
38
|
+
contextBridge.exposeInMainWorld('electronAPI', {
|
|
39
|
+
// File operations — exposed as async functions
|
|
40
|
+
readFile: (path: string) => ipcRenderer.invoke('file:read', path),
|
|
41
|
+
writeFile: (path: string, data: string) => ipcRenderer.invoke('file:write', path, data),
|
|
42
|
+
selectFile: () => ipcRenderer.invoke('dialog:openFile'),
|
|
43
|
+
|
|
44
|
+
// App info
|
|
45
|
+
getVersion: () => ipcRenderer.invoke('app:version'),
|
|
46
|
+
|
|
47
|
+
// One-way events (renderer -> main)
|
|
48
|
+
minimize: () => ipcRenderer.send('window:minimize'),
|
|
49
|
+
maximize: () => ipcRenderer.send('window:maximize'),
|
|
50
|
+
|
|
51
|
+
// Subscriptions (main -> renderer)
|
|
52
|
+
onUpdateAvailable: (callback: (info: UpdateInfo) => void) => {
|
|
53
|
+
const handler = (_event: Event, info: UpdateInfo) => callback(info);
|
|
54
|
+
ipcRenderer.on('update:available', handler);
|
|
55
|
+
return () => ipcRenderer.removeListener('update:available', handler);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Type declaration for renderer
|
|
62
|
+
declare global {
|
|
63
|
+
interface Window {
|
|
64
|
+
electronAPI: {
|
|
65
|
+
readFile: (path: string) => Promise<string>;
|
|
66
|
+
writeFile: (path: string, data: string) => Promise<void>;
|
|
67
|
+
selectFile: () => Promise<string | null>;
|
|
68
|
+
getVersion: () => Promise<string>;
|
|
69
|
+
minimize: () => void;
|
|
70
|
+
maximize: () => void;
|
|
71
|
+
onUpdateAvailable: (callback: (info: UpdateInfo) => void) => () => void;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. IPC Handlers — Main Process
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// main.ts
|
|
81
|
+
import { app, BrowserWindow, ipcMain, dialog } from 'electron';
|
|
82
|
+
import fs from 'fs/promises';
|
|
83
|
+
import path from 'path';
|
|
84
|
+
|
|
85
|
+
// Invoke handlers (bidirectional — returns a value)
|
|
86
|
+
ipcMain.handle('file:read', async (_event, filePath: string) => {
|
|
87
|
+
// Validate path to prevent directory traversal
|
|
88
|
+
const resolved = path.resolve(filePath);
|
|
89
|
+
if (!resolved.startsWith(app.getPath('userData'))) {
|
|
90
|
+
throw new Error('Access denied: path outside user data directory');
|
|
91
|
+
}
|
|
92
|
+
return fs.readFile(resolved, 'utf-8');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
ipcMain.handle('dialog:openFile', async () => {
|
|
96
|
+
const result = await dialog.showOpenDialog({
|
|
97
|
+
properties: ['openFile'],
|
|
98
|
+
filters: [{ name: 'Text', extensions: ['txt', 'md', 'json'] }],
|
|
99
|
+
});
|
|
100
|
+
return result.canceled ? null : result.filePaths[0];
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
ipcMain.handle('app:version', () => app.getVersion());
|
|
104
|
+
|
|
105
|
+
// One-way handlers
|
|
106
|
+
ipcMain.on('window:minimize', (event) => {
|
|
107
|
+
BrowserWindow.fromWebContents(event.sender)?.minimize();
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 4. Window Management
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
function createMainWindow(): BrowserWindow {
|
|
115
|
+
const win = new BrowserWindow({
|
|
116
|
+
width: 1200,
|
|
117
|
+
height: 800,
|
|
118
|
+
minWidth: 800,
|
|
119
|
+
minHeight: 600,
|
|
120
|
+
titleBarStyle: 'hiddenInset', // macOS frameless with traffic lights
|
|
121
|
+
webPreferences: {
|
|
122
|
+
preload: path.join(__dirname, 'preload.js'),
|
|
123
|
+
contextIsolation: true, // REQUIRED — never disable
|
|
124
|
+
nodeIntegration: false, // REQUIRED — never enable
|
|
125
|
+
sandbox: true, // extra isolation
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Save/restore window position
|
|
130
|
+
const bounds = store.get('windowBounds');
|
|
131
|
+
if (bounds) win.setBounds(bounds);
|
|
132
|
+
|
|
133
|
+
win.on('close', () => {
|
|
134
|
+
store.set('windowBounds', win.getBounds());
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (isDev) {
|
|
138
|
+
win.loadURL('http://localhost:3000');
|
|
139
|
+
win.webContents.openDevTools();
|
|
140
|
+
} else {
|
|
141
|
+
win.loadFile(path.join(__dirname, '../renderer/index.html'));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return win;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 5. Auto-Update
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { autoUpdater } from 'electron-updater';
|
|
152
|
+
import log from 'electron-log';
|
|
153
|
+
|
|
154
|
+
autoUpdater.logger = log;
|
|
155
|
+
autoUpdater.autoDownload = false; // let user decide
|
|
156
|
+
|
|
157
|
+
export function setupAutoUpdater(mainWindow: BrowserWindow) {
|
|
158
|
+
autoUpdater.on('update-available', (info) => {
|
|
159
|
+
mainWindow.webContents.send('update:available', {
|
|
160
|
+
version: info.version,
|
|
161
|
+
releaseNotes: info.releaseNotes,
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
autoUpdater.on('download-progress', (progress) => {
|
|
166
|
+
mainWindow.webContents.send('update:progress', {
|
|
167
|
+
percent: progress.percent,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
autoUpdater.on('update-downloaded', () => {
|
|
172
|
+
mainWindow.webContents.send('update:ready');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Check every 4 hours
|
|
176
|
+
setInterval(() => autoUpdater.checkForUpdates(), 4 * 60 * 60 * 1000);
|
|
177
|
+
autoUpdater.checkForUpdates();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// IPC handler for user-initiated download/install
|
|
181
|
+
ipcMain.handle('update:download', () => autoUpdater.downloadUpdate());
|
|
182
|
+
ipcMain.handle('update:install', () => autoUpdater.quitAndInstall());
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 6. Security Hardening
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// main.ts — security headers
|
|
189
|
+
app.on('web-contents-created', (_event, contents) => {
|
|
190
|
+
// Prevent navigation away from the app
|
|
191
|
+
contents.on('will-navigate', (event, url) => {
|
|
192
|
+
const parsed = new URL(url);
|
|
193
|
+
if (parsed.origin !== 'http://localhost:3000' && parsed.protocol !== 'file:') {
|
|
194
|
+
event.preventDefault();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Prevent new windows (open links in default browser)
|
|
199
|
+
contents.setWindowOpenHandler(({ url }) => {
|
|
200
|
+
shell.openExternal(url);
|
|
201
|
+
return { action: 'deny' };
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Content Security Policy
|
|
206
|
+
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
|
207
|
+
callback({
|
|
208
|
+
responseHeaders: {
|
|
209
|
+
...details.responseHeaders,
|
|
210
|
+
'Content-Security-Policy': [
|
|
211
|
+
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 7. Packaging — electron-builder
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"build": {
|
|
223
|
+
"appId": "com.myapp.desktop",
|
|
224
|
+
"productName": "MyApp",
|
|
225
|
+
"mac": {
|
|
226
|
+
"target": ["dmg", "zip"],
|
|
227
|
+
"category": "public.app-category.developer-tools",
|
|
228
|
+
"hardenedRuntime": true,
|
|
229
|
+
"notarize": true
|
|
230
|
+
},
|
|
231
|
+
"win": {
|
|
232
|
+
"target": ["nsis"],
|
|
233
|
+
"certificateSubjectName": "My Company"
|
|
234
|
+
},
|
|
235
|
+
"linux": {
|
|
236
|
+
"target": ["AppImage", "deb"],
|
|
237
|
+
"category": "Development"
|
|
238
|
+
},
|
|
239
|
+
"publish": {
|
|
240
|
+
"provider": "github"
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Examples
|
|
247
|
+
|
|
248
|
+
| Pattern | Process | Purpose |
|
|
249
|
+
|---------|---------|---------|
|
|
250
|
+
| `contextBridge.exposeInMainWorld` | Preload | Safe API for renderer |
|
|
251
|
+
| `ipcMain.handle` / `ipcRenderer.invoke` | Main/Renderer | Bidirectional async IPC |
|
|
252
|
+
| `autoUpdater` | Main | Seamless updates |
|
|
253
|
+
| `BrowserWindow` options | Main | Security + UX config |
|
|
254
|
+
|
|
255
|
+
## Checklist
|
|
256
|
+
|
|
257
|
+
- [ ] `contextIsolation: true` and `nodeIntegration: false` on all windows
|
|
258
|
+
- [ ] `sandbox: true` enabled for extra process isolation
|
|
259
|
+
- [ ] All renderer-to-main communication goes through preload + IPC
|
|
260
|
+
- [ ] IPC handlers validate and sanitize all arguments
|
|
261
|
+
- [ ] File paths validated against allowed directories (no traversal)
|
|
262
|
+
- [ ] Navigation and new-window events restricted
|
|
263
|
+
- [ ] CSP headers set to prevent XSS
|
|
264
|
+
- [ ] Auto-updater configured with code signing
|
|
265
|
+
- [ ] macOS builds notarized, Windows builds code-signed
|
|
266
|
+
- [ ] Dev tools disabled in production builds
|