@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,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: background-jobs
|
|
3
|
+
description: Background job patterns with BullMQ, Celery, Sidekiq, retry strategies, priority queues, and rate limiting.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Background Job Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
Move work to background jobs when it is slow (API calls, email, PDF generation), not needed for the user's immediate response, or must be retried on failure. This skill covers job queue design across BullMQ (Node.js), Celery (Python), and Sidekiq (Ruby), plus universal patterns for retries, priorities, rate limiting, and monitoring.
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
### BullMQ (Node.js) -- Queue + Worker
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Queue, Worker, Job } from "bullmq";
|
|
17
|
+
import IORedis from "ioredis";
|
|
18
|
+
|
|
19
|
+
const connection = new IORedis({ host: "localhost", port: 6379, maxRetriesPerRequest: null });
|
|
20
|
+
const emailQueue = new Queue("emails", { connection });
|
|
21
|
+
|
|
22
|
+
// Enqueue a job
|
|
23
|
+
await emailQueue.add("welcome", {
|
|
24
|
+
to: "alice@example.com",
|
|
25
|
+
template: "welcome",
|
|
26
|
+
variables: { name: "Alice" },
|
|
27
|
+
}, {
|
|
28
|
+
priority: 1,
|
|
29
|
+
attempts: 5,
|
|
30
|
+
backoff: { type: "exponential", delay: 5000 },
|
|
31
|
+
removeOnComplete: { count: 1000 },
|
|
32
|
+
removeOnFail: { age: 7 * 86400 },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Worker -- process jobs
|
|
36
|
+
const worker = new Worker("emails", async (job: Job) => {
|
|
37
|
+
switch (job.name) {
|
|
38
|
+
case "welcome":
|
|
39
|
+
await sendEmail(job.data.to, job.data.template, job.data.variables);
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`Unknown job: ${job.name}`);
|
|
43
|
+
}
|
|
44
|
+
}, {
|
|
45
|
+
connection,
|
|
46
|
+
concurrency: 5,
|
|
47
|
+
limiter: { max: 50, duration: 60_000 },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
worker.on("failed", (job, err) => {
|
|
51
|
+
console.error(`Job ${job?.id} failed: ${err.message}`);
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Celery (Python) -- Distributed Tasks
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from celery import Celery
|
|
59
|
+
|
|
60
|
+
app = Celery("tasks", broker="redis://localhost:6379/0")
|
|
61
|
+
|
|
62
|
+
app.conf.update(
|
|
63
|
+
task_serializer="json",
|
|
64
|
+
task_acks_late=True,
|
|
65
|
+
worker_prefetch_multiplier=1,
|
|
66
|
+
task_reject_on_worker_lost=True,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
@app.task(bind=True, max_retries=5, autoretry_for=(ConnectionError, TimeoutError),
|
|
70
|
+
retry_backoff=True, retry_backoff_max=3600)
|
|
71
|
+
def process_payment(self, order_id: str):
|
|
72
|
+
order = Order.objects.get(id=order_id)
|
|
73
|
+
try:
|
|
74
|
+
charge = gateway.charge(order.total, order.payment_method)
|
|
75
|
+
order.mark_paid(charge.id)
|
|
76
|
+
except PaymentDeclined:
|
|
77
|
+
order.mark_declined()
|
|
78
|
+
raise # don't retry business failures
|
|
79
|
+
|
|
80
|
+
process_payment.apply_async(args=["order-123"], queue="payments", priority=0)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Sidekiq (Ruby) -- Threaded Workers
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
class WelcomeEmailJob
|
|
87
|
+
include Sidekiq::Job
|
|
88
|
+
|
|
89
|
+
sidekiq_options queue: :mailers, retry: 5, backtrace: true
|
|
90
|
+
|
|
91
|
+
sidekiq_retry_in do |count, _exception|
|
|
92
|
+
(count ** 4) + 15 + (rand(10) * (count + 1))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def perform(user_id)
|
|
96
|
+
user = User.find(user_id)
|
|
97
|
+
UserMailer.welcome(user).deliver_now
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
WelcomeEmailJob.perform_async(user.id)
|
|
102
|
+
WelcomeEmailJob.perform_in(5.minutes, user.id)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Retry Strategies
|
|
106
|
+
|
|
107
|
+
| Strategy | Formula | Use Case |
|
|
108
|
+
|----------|---------|----------|
|
|
109
|
+
| Fixed delay | 30s every time | Simple rate-limited APIs |
|
|
110
|
+
| Exponential backoff | `delay * 2^attempt` | Transient failures |
|
|
111
|
+
| Exponential + jitter | `delay * 2^attempt + random(0, delay)` | Prevent thundering herd |
|
|
112
|
+
| Custom schedule | `[10s, 30s, 2m, 10m, 1h]` | Known recovery patterns |
|
|
113
|
+
|
|
114
|
+
Do not retry: 400 Bad Request, 401/403, 404, or business logic failures (declined payment, validation error).
|
|
115
|
+
|
|
116
|
+
### Job Design Rules
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// GOOD: Pass IDs, not objects -- jobs are serialized to JSON
|
|
120
|
+
await queue.add("process-order", { orderId: "abc-123" });
|
|
121
|
+
|
|
122
|
+
// BAD: Passing the full object -- stale data, large payloads
|
|
123
|
+
await queue.add("process-order", { order: fullOrderObject });
|
|
124
|
+
|
|
125
|
+
// GOOD: Idempotent -- safe to run twice
|
|
126
|
+
async function processOrder(job: Job) {
|
|
127
|
+
const order = await db.orders.findUnique({ where: { id: job.data.orderId } });
|
|
128
|
+
if (order.status === "processed") return;
|
|
129
|
+
await chargeAndFulfill(order);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Rate Limiting
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// BullMQ: 100 API calls per minute per tenant
|
|
137
|
+
const apiQueue = new Queue("external-api", { connection });
|
|
138
|
+
|
|
139
|
+
await apiQueue.add("sync-crm", { tenantId, data }, {
|
|
140
|
+
group: { id: tenantId },
|
|
141
|
+
limiter: { max: 100, duration: 60_000 },
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Examples
|
|
146
|
+
|
|
147
|
+
| Framework | Language | Backend | Unique Feature |
|
|
148
|
+
|-----------|----------|---------|----------------|
|
|
149
|
+
| BullMQ | TypeScript | Redis | Flow (parent-child jobs) |
|
|
150
|
+
| Celery | Python | Redis/RabbitMQ | Canvas (chord, chain, group) |
|
|
151
|
+
| Sidekiq | Ruby | Redis | Batches (pro), threading |
|
|
152
|
+
| Temporal | Any (via SDK) | Temporal Server | Durable execution, sagas |
|
|
153
|
+
|
|
154
|
+
## Checklist
|
|
155
|
+
- [ ] Jobs are idempotent -- safe to process the same job twice
|
|
156
|
+
- [ ] Jobs pass IDs, not serialized objects (re-fetch fresh data)
|
|
157
|
+
- [ ] Retry strategy uses exponential backoff with jitter
|
|
158
|
+
- [ ] Non-retryable errors (4xx, business failures) are not retried
|
|
159
|
+
- [ ] Dead letter queue captures permanently failed jobs
|
|
160
|
+
- [ ] Rate limiting configured per external API or tenant
|
|
161
|
+
- [ ] Priority levels defined and documented
|
|
162
|
+
- [ ] Job queue dashboard deployed (Bull Board, Flower, Sidekiq Web)
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-extensions
|
|
3
|
+
description: Browser extension patterns for Manifest V3, content scripts, background service workers, cross-script messaging, and storage.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Browser Extension Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
Build a browser extension when you need to modify web page behavior, add UI overlays, intercept network requests, or provide cross-site functionality. These patterns cover Manifest V3 (required for Chrome), content scripts that run in page context, background service workers for event handling, popup and side panel UIs, secure messaging between contexts, and persistent storage.
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
### Manifest V3 Configuration
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"manifest_version": 3,
|
|
18
|
+
"name": "My Extension",
|
|
19
|
+
"version": "1.0.0",
|
|
20
|
+
"description": "A productivity extension",
|
|
21
|
+
"permissions": ["storage", "activeTab", "contextMenus", "alarms"],
|
|
22
|
+
"host_permissions": ["https://*.example.com/*"],
|
|
23
|
+
"background": {
|
|
24
|
+
"service_worker": "background.js",
|
|
25
|
+
"type": "module"
|
|
26
|
+
},
|
|
27
|
+
"content_scripts": [
|
|
28
|
+
{
|
|
29
|
+
"matches": ["https://*.example.com/*"],
|
|
30
|
+
"js": ["content.js"],
|
|
31
|
+
"css": ["content.css"],
|
|
32
|
+
"run_at": "document_idle"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"action": {
|
|
36
|
+
"default_popup": "popup.html",
|
|
37
|
+
"default_icon": {
|
|
38
|
+
"16": "icons/16.png",
|
|
39
|
+
"48": "icons/48.png",
|
|
40
|
+
"128": "icons/128.png"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"side_panel": {
|
|
44
|
+
"default_path": "sidepanel.html"
|
|
45
|
+
},
|
|
46
|
+
"options_page": "options.html",
|
|
47
|
+
"icons": {
|
|
48
|
+
"16": "icons/16.png",
|
|
49
|
+
"48": "icons/48.png",
|
|
50
|
+
"128": "icons/128.png"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Background Service Worker
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// background.ts
|
|
59
|
+
chrome.runtime.onInstalled.addListener((details) => {
|
|
60
|
+
if (details.reason === 'install') {
|
|
61
|
+
chrome.storage.local.set({ settings: { enabled: true, theme: 'auto' } });
|
|
62
|
+
|
|
63
|
+
// Create context menu
|
|
64
|
+
chrome.contextMenus.create({
|
|
65
|
+
id: 'analyze-selection',
|
|
66
|
+
title: 'Analyze "%s"',
|
|
67
|
+
contexts: ['selection'],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Context menu click handler
|
|
73
|
+
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
|
|
74
|
+
if (info.menuItemId === 'analyze-selection' && tab?.id) {
|
|
75
|
+
const result = await analyzeText(info.selectionText ?? '');
|
|
76
|
+
chrome.tabs.sendMessage(tab.id, { type: 'ANALYSIS_RESULT', data: result });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Message handler for content script / popup communication
|
|
81
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
82
|
+
if (message.type === 'GET_DATA') {
|
|
83
|
+
fetchData(message.url)
|
|
84
|
+
.then((data) => sendResponse({ success: true, data }))
|
|
85
|
+
.catch((err) => sendResponse({ success: false, error: err.message }));
|
|
86
|
+
return true; // keep message channel open for async response
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (message.type === 'SAVE_SETTING') {
|
|
90
|
+
chrome.storage.sync.set({ [message.key]: message.value });
|
|
91
|
+
sendResponse({ success: true });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Alarm for periodic tasks
|
|
96
|
+
chrome.alarms.create('sync-data', { periodInMinutes: 30 });
|
|
97
|
+
chrome.alarms.onAlarm.addListener(async (alarm) => {
|
|
98
|
+
if (alarm.name === 'sync-data') {
|
|
99
|
+
await syncDataToServer();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Content Script
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// content.ts
|
|
108
|
+
class PageEnhancer {
|
|
109
|
+
private overlay: HTMLDivElement | null = null;
|
|
110
|
+
|
|
111
|
+
constructor() {
|
|
112
|
+
this.init();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async init() {
|
|
116
|
+
const { settings } = await chrome.storage.local.get('settings');
|
|
117
|
+
if (!settings?.enabled) return;
|
|
118
|
+
|
|
119
|
+
this.injectUI();
|
|
120
|
+
this.observeDOM();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private injectUI() {
|
|
124
|
+
// Use Shadow DOM to isolate styles
|
|
125
|
+
const host = document.createElement('div');
|
|
126
|
+
host.id = 'my-extension-root';
|
|
127
|
+
const shadow = host.attachShadow({ mode: 'closed' });
|
|
128
|
+
|
|
129
|
+
const style = document.createElement('style');
|
|
130
|
+
style.textContent = `
|
|
131
|
+
.overlay { position: fixed; bottom: 16px; right: 16px; z-index: 999999;
|
|
132
|
+
background: white; border-radius: 8px; padding: 16px;
|
|
133
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15); font-family: system-ui; }
|
|
134
|
+
.overlay button { background: #2563eb; color: white; border: none;
|
|
135
|
+
padding: 8px 16px; border-radius: 4px; cursor: pointer; }
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
this.overlay = document.createElement('div');
|
|
139
|
+
this.overlay.className = 'overlay';
|
|
140
|
+
this.overlay.innerHTML = `<p>Extension Active</p><button id="action-btn">Analyze</button>`;
|
|
141
|
+
|
|
142
|
+
shadow.appendChild(style);
|
|
143
|
+
shadow.appendChild(this.overlay);
|
|
144
|
+
document.body.appendChild(host);
|
|
145
|
+
|
|
146
|
+
shadow.getElementById('action-btn')?.addEventListener('click', () => this.analyze());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private observeDOM() {
|
|
150
|
+
const observer = new MutationObserver((mutations) => {
|
|
151
|
+
for (const mutation of mutations) {
|
|
152
|
+
for (const node of mutation.addedNodes) {
|
|
153
|
+
if (node instanceof HTMLElement && node.matches('.target-element')) {
|
|
154
|
+
this.processElement(node);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private async analyze() {
|
|
163
|
+
const response = await chrome.runtime.sendMessage({
|
|
164
|
+
type: 'GET_DATA',
|
|
165
|
+
url: window.location.href,
|
|
166
|
+
});
|
|
167
|
+
if (response.success) {
|
|
168
|
+
this.showResults(response.data);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private processElement(el: HTMLElement) { /* enhance element */ }
|
|
173
|
+
private showResults(data: unknown) { /* display in overlay */ }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Listen for messages from background
|
|
177
|
+
chrome.runtime.onMessage.addListener((message) => {
|
|
178
|
+
if (message.type === 'ANALYSIS_RESULT') {
|
|
179
|
+
new PageEnhancer().showResults(message.data);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
new PageEnhancer();
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Popup UI
|
|
187
|
+
|
|
188
|
+
```html
|
|
189
|
+
<!-- popup.html -->
|
|
190
|
+
<!DOCTYPE html>
|
|
191
|
+
<html>
|
|
192
|
+
<head><link rel="stylesheet" href="popup.css" /></head>
|
|
193
|
+
<body>
|
|
194
|
+
<div id="app">
|
|
195
|
+
<h2>My Extension</h2>
|
|
196
|
+
<label><input type="checkbox" id="enabled" /> Enabled</label>
|
|
197
|
+
<div id="stats"></div>
|
|
198
|
+
<button id="analyze">Analyze Current Page</button>
|
|
199
|
+
</div>
|
|
200
|
+
<script src="popup.js" type="module"></script>
|
|
201
|
+
</body>
|
|
202
|
+
</html>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// popup.ts
|
|
207
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
208
|
+
const { settings } = await chrome.storage.local.get('settings');
|
|
209
|
+
const enabledCheckbox = document.getElementById('enabled') as HTMLInputElement;
|
|
210
|
+
enabledCheckbox.checked = settings?.enabled ?? true;
|
|
211
|
+
|
|
212
|
+
enabledCheckbox.addEventListener('change', () => {
|
|
213
|
+
chrome.storage.local.set({ settings: { ...settings, enabled: enabledCheckbox.checked } });
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
document.getElementById('analyze')?.addEventListener('click', async () => {
|
|
217
|
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
218
|
+
if (tab?.id) {
|
|
219
|
+
chrome.tabs.sendMessage(tab.id, { type: 'TRIGGER_ANALYSIS' });
|
|
220
|
+
window.close();
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Storage Patterns
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// storage.ts — typed wrapper around chrome.storage
|
|
230
|
+
interface ExtensionStorage {
|
|
231
|
+
settings: { enabled: boolean; theme: 'light' | 'dark' | 'auto' };
|
|
232
|
+
cache: Record<string, { data: unknown; expires: number }>;
|
|
233
|
+
stats: { pagesAnalyzed: number; lastActive: string };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function getStorage<K extends keyof ExtensionStorage>(
|
|
237
|
+
key: K
|
|
238
|
+
): Promise<ExtensionStorage[K] | undefined> {
|
|
239
|
+
const result = await chrome.storage.local.get(key);
|
|
240
|
+
return result[key];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function setStorage<K extends keyof ExtensionStorage>(
|
|
244
|
+
key: K,
|
|
245
|
+
value: ExtensionStorage[K]
|
|
246
|
+
): Promise<void> {
|
|
247
|
+
await chrome.storage.local.set({ [key]: value });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Listen for storage changes
|
|
251
|
+
chrome.storage.onChanged.addListener((changes, area) => {
|
|
252
|
+
if (area === 'local' && changes.settings) {
|
|
253
|
+
const newSettings = changes.settings.newValue;
|
|
254
|
+
applySettings(newSettings);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Cached data with expiry
|
|
259
|
+
async function getCachedData(key: string): Promise<unknown | null> {
|
|
260
|
+
const cache = await getStorage('cache') ?? {};
|
|
261
|
+
const entry = cache[key];
|
|
262
|
+
if (!entry || entry.expires < Date.now()) return null;
|
|
263
|
+
return entry.data;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function setCachedData(key: string, data: unknown, ttlMs: number = 3600000) {
|
|
267
|
+
const cache = await getStorage('cache') ?? {};
|
|
268
|
+
cache[key] = { data, expires: Date.now() + ttlMs };
|
|
269
|
+
await setStorage('cache', cache);
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Build Setup (Vite + CRXJS)
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// vite.config.ts
|
|
277
|
+
import { defineConfig } from 'vite';
|
|
278
|
+
import react from '@vitejs/plugin-react';
|
|
279
|
+
import crx from '@crxjs/vite-plugin';
|
|
280
|
+
import manifest from './manifest.json';
|
|
281
|
+
|
|
282
|
+
export default defineConfig({
|
|
283
|
+
plugins: [react(), crx({ manifest })],
|
|
284
|
+
build: {
|
|
285
|
+
rollupOptions: {
|
|
286
|
+
input: { popup: 'popup.html', sidepanel: 'sidepanel.html', options: 'options.html' },
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Examples
|
|
293
|
+
|
|
294
|
+
| Context | Access | Communication |
|
|
295
|
+
|---------|--------|---------------|
|
|
296
|
+
| Background (service worker) | Full Chrome APIs, no DOM | `chrome.runtime.onMessage` |
|
|
297
|
+
| Content script | Page DOM, limited Chrome APIs | `chrome.runtime.sendMessage` |
|
|
298
|
+
| Popup / Side panel | Full Chrome APIs, own DOM | `chrome.tabs.sendMessage` |
|
|
299
|
+
| Injected script | Page JS context | `window.postMessage` |
|
|
300
|
+
|
|
301
|
+
## Checklist
|
|
302
|
+
- [ ] Manifest V3 used (V2 deprecated in Chrome)
|
|
303
|
+
- [ ] Permissions are minimal — only request what is needed
|
|
304
|
+
- [ ] Content script styles isolated with Shadow DOM
|
|
305
|
+
- [ ] Background script handles async messages with `return true`
|
|
306
|
+
- [ ] Storage uses typed wrapper with `chrome.storage.local` or `sync`
|
|
307
|
+
- [ ] Context menus created in `onInstalled` handler, not on every load
|
|
308
|
+
- [ ] Popup and options pages work without content script injection
|
|
309
|
+
- [ ] Extension tested in Chrome, Firefox (WebExtensions API), and Edge
|