עולם ההרחבות — מדריך ארכיטקטורה מלא
מסמך זה מפרט את כל המערכת סביב הרחבות בפלטפורמת מדד — מהשלב שבו מפתח כותב קוד ועד שמשתמש קצה מפעיל הרחבה בדשבורד.
תוכן עניינים
- סקירה כללית
- מבנה חובה של הרחבה
- סביבת הריצה (Runtime)
- Bridge API — תקשורת הרחבה ↔ פלטפורמה
- הרשאות (Permissions)
- אחסון מפתח-ערך (Store)
- אחסון ישויות (Entity Storage)
- גישה, רישוי ומגבלות
- שליחת מייל (SEND_EMAIL)
- תזמון משימות (Scheduler)
- קריאה ל-AI (AI_COMPLETE)
- קריאות HTTP חיצוניות (FETCH_EXTERNAL)
- הדפסה, PDF והורדת קבצים
- הגדרת Config ו-Extension Data
- הצהרות @extension-entities ו-@extension-config
- מחזור חיי פיתוח — מפתח → חנות → משתמש
- חנות ההרחבות וקטגוריות
- פרומפט הפלטפורמה (Platform Prompt)
- מודלי בסיס נתונים — מפת Prisma
1. סקירה כללית
הרחבה (Extension) היא קובץ JavaScript יחיד (bundle) שרץ בתוך iframe מבודד. ההרחבה מתקשרת עם הפלטפורמה אך ורק דרך ה-Bridge API — ממשק הודעות מבוסס postMessage.
┌─────────────────────────────────────────────────┐
│ פלטפורמת מדד (Parent) │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ iframe (sandbox) │ │
│ │ │ │
│ │ window.MyExtension = { │ │
│ │ metadata: { ... }, │ │
│ │ mount(containerId, bridge) { ... }, │ │
│ │ unmount() { ... } │ │
│ │ } │ │
│ │ │ │
│ │ bridge.send({ type: "STORE_GET", ... }) ───┼─┤──► API route
│ │ bridge.on("STORE_GET_RESPONSE", fn) ◄──┼─┤──◄ Response
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
עקרונות ארכיטקטוניים:
- בידוד מלא — ההרחבה רצה ב-iframe עם
sandboxattributes. אין גישה ישירה ל-DOM, cookies, או localStorage של הפלטפורמה. - הרשאות מפורשות — כל פקודה דורשת הצהרה ב-
metadata.permissions. - ריבוי דיירים — הנתונים מבודדים לפי
extensionSlug + companyId. הרחבה אחת לא רואה נתונים של חברה אחרת. - RTL תמיד — הפלטפורמה מיועדת לשוק ישראלי-ערבי. כל ה-UI הוא RTL.
2. מבנה חובה של הרחבה
כל bundle חייב להגדיר window.MyExtension עם שלושה שדות:
/* @extension-entities
[
{ "type": "contact", "label": "איש קשר", "description": "...", "recommendLimit": true }
]
*/
/* @extension-config
[
{ "key": "maxItems", "type": "number", "default": 10, "label": "מקסימום פריטים" }
]
*/
window.MyExtension = {
metadata: {
name: "שם ההרחבה",
version: "1.0.0",
permissions: ["GET_CURRENT_USER", "STORE_GET", "STORE_SET"],
// אופציונלי:
locales: ["he", "ar"],
defaultLocale: "he",
dependencies: [{ slug: "other-ext", version: "1.0.0" }],
},
mount(containerId, bridge) {
var root = document.getElementById(containerId);
// ... בניית ה-UI
},
unmount() {
// ניקוי משאבים
},
};
| שדה | חובה | תיאור |
|---|---|---|
metadata.name | ✅ | שם ההרחבה |
metadata.version | ✅ | גרסה (semver) |
metadata.permissions | ✅ | מערך הרשאות Bridge API |
metadata.locales | ❌ | שפות נתמכות (אם רב-לשוני) |
metadata.defaultLocale | ❌ | שפת ברירת מחדל |
metadata.dependencies | ❌ | תלויות בהרחבות אחרות |
mount(containerId, bridge) | ✅ | פונקציית הרצה — containerId תמיד "ext-container" |
unmount() | ✅ | פונקציית ניקוי |
כללים:
- אסור
import/export— הקובץ הוא bundle עצמאי. - אסור
(function(){ ... })()— חובהwindow.MyExtension = { ... }ישירות. - אסור
localStorage/document.cookie— השתמש ב-STORE_*דרך Bridge. - אסור
fetchישיר — השתמש ב-FETCH_EXTERNALדרך Bridge.
3. סביבת הריצה (Runtime)
קבצים ראשיים
| קובץ | תפקיד |
|---|---|
src/app/sandbox-frame/page.tsx | עמוד ה-iframe — טוען ומריץ את ה-bundle |
src/app/dashboard/extensions/[slug]/page.tsx | Runner ייצור — צד ה-parent |
src/app/dashboard/extensions/beta/[assignmentId]/page.tsx | Runner בטא |
src/app/admin/sandbox/page.tsx | Sandbox אדמין |
src/app/dashboard/developer/sandbox/page.tsx | Sandbox מפתח |
זרימת הטעינה
1. משתמש נכנס ל-/dashboard/extensions/[slug]
│
2. Parent קורא /api/my-extensions/[slug]
│ → בדיקת גישה, ספירת שימוש, טעינת config
│
3. Parent מרנדר iframe ל-/sandbox-frame?url=<bundleUrl>&theme=<theme>
│ sandbox="allow-scripts allow-forms allow-same-origin allow-modals allow-popups allow-downloads"
│
4. sandbox-frame מריץ fetch(bundleUrl) → new Function(code)()
│ → Watchdog 5 שניות: אם window.MyExtension לא נמצא — timeout
│
5. נבנה אובייקט bridge:
│ • bridge.send(msg) → window.parent.postMessage(msg, "*")
│ • bridge.on(type, cb) → רישום listener
│
6. ext.mount("ext-container", bridge)
│
7. הודעות Bridge נשלחות/מתקבלות דרך postMessage
ערכות נושא (Theme)
ההרחבה מקבלת Theme דרך query param:
var theme = new URL(location.href).searchParams.get("theme"); // "light" | "dark" | "black"
4. Bridge API — תקשורת הרחבה ↔ פלטפורמה
כל התקשורת עוברת דרך bridge.send() (שליחה) ו-bridge.on() (האזנה לתגובה).
טבלת פקודות מלאה
| פקודה (type) | פרמטרים | תגובה (_RESPONSE) | שדות בתגובה | API Route |
|---|---|---|---|---|
SHOW_TOAST | text, kind (success/error/info) | — | — | צד לקוח |
NAVIGATE | to (נתיב /dashboard/...) | — | — | צד לקוח |
OPEN_NEW_WINDOW | url (http/https בלבד) | OPEN_NEW_WINDOW_RESPONSE | ok / error | צד לקוח |
GET_CURRENT_USER | — | GET_CURRENT_USER_RESPONSE | id, name, email, company, companyId | מה-session |
GET_COMPANY_USERS | — | GET_COMPANY_USERS_RESPONSE | companyId, companyName, users[] (id, name, email, role) | /api/extension-bridge/company-users |
GET_MY_EXTENSION_CONFIG | — | GET_MY_EXTENSION_CONFIG_RESPONSE | config | מנתונים טעונים |
GET_EXTENSION_DATA | slug, version? | GET_EXTENSION_DATA_RESPONSE | data | /api/developer/extension-data |
GET_ACCESS_STATUS | — | GET_ACCESS_STATUS_RESPONSE | status, isActive, isFrozen, isExpired, usageCount, usageLimit, entityCount, entityLimit, limitMode, limitEntityType, lastUsedAt, grantedAt, expiresAt | /api/extension-bridge/access-status |
STORE_SET | key, value | STORE_SET_RESPONSE | ok | /api/extension-bridge/store |
STORE_GET | key | STORE_GET_RESPONSE | key, value | /api/extension-bridge/store |
STORE_DELETE | key | STORE_DELETE_RESPONSE | ok | /api/extension-bridge/store |
STORE_LIST | prefix | STORE_LIST_RESPONSE | keys | /api/extension-bridge/store |
ENTITY_CREATE | entityType, data | ENTITY_CREATE_RESPONSE | ok, entity / error | /api/extension-bridge/entity |
ENTITY_LIST | entityType, filter? | ENTITY_LIST_RESPONSE | ok, entities[], total | /api/extension-bridge/entity |
ENTITY_UPDATE | entityType, entityId, data | ENTITY_UPDATE_RESPONSE | ok, entity / error | /api/extension-bridge/entity |
ENTITY_DELETE | entityType, entityId | ENTITY_DELETE_RESPONSE | ok / error | /api/extension-bridge/entity |
ENTITY_COUNT | entityType | ENTITY_COUNT_RESPONSE | ok, count | /api/extension-bridge/entity |
FETCH_EXTERNAL | url, method?, headers?, body? | FETCH_EXTERNAL_RESPONSE | status, data / error | /api/extension-bridge/fetch |
SEND_EMAIL | to, subject, html?, text?, replyTo? | SEND_EMAIL_RESPONSE | ok, sent, dailySent, dailyLimit / error | /api/extension-bridge/send-email |
SCHEDULE_CREATE | actionType, triggerAt, payload, ref? | SCHEDULE_CREATE_RESPONSE | ok, scheduleId / error | /api/extension-bridge/scheduler |
SCHEDULE_LIST | — | SCHEDULE_LIST_RESPONSE | ok, items[] | /api/extension-bridge/scheduler |
SCHEDULE_DELETE | scheduleId | SCHEDULE_DELETE_RESPONSE | ok / error | /api/extension-bridge/scheduler |
AI_COMPLETE | prompt, maxTokens? | AI_COMPLETE_RESPONSE | ok, text / error | /api/extension-bridge/ai |
PRINT_HTML | html | PRINT_HTML_RESPONSE | ok / error | צד לקוח |
GENERATE_PDF | html | GENERATE_PDF_RESPONSE | ok, info / error | צד לקוח |
DOWNLOAD_FILE | filename, content, mimeType | DOWNLOAD_FILE_RESPONSE | ok / error | צד לקוח |
REPORT_USAGE | amount? (ברירת מחדל: 1) | REPORT_USAGE_RESULT | ok, usageCount, usageLimit / error | /api/extensions/[slug]/report-usage |
תבנית שימוש בסיסית
// שלב 1: רשום listener לתגובה
bridge.on("STORE_GET_RESPONSE", function(res) {
if (res.error) { console.error(res.error); return; }
console.log(res.key, res.value);
});
// שלב 2: שלח פקודה
bridge.send({ type: "STORE_GET", key: "myData" });
5. הרשאות (Permissions)
קובץ מרכזי: src/lib/extensionPermissions.ts
כל הרחבה מצהירה אילו פקודות Bridge היא צריכה ב-metadata.permissions. פקודות שלא הוצהרו ייחסמו.
| הרשאה | תיאור | פנימית? |
|---|---|---|
GET_CURRENT_USER | קרא מידע מזוהה על המשתמש | |
GET_COMPANY_USERS | רשימת משתמשים בחברה + תפקידים | |
GET_MY_EXTENSION_CONFIG | הגדרות config שמורות | |
GET_EXTENSION_DATA | נתונים מ-contractJson | |
SHOW_TOAST | הודעות Toast | |
NAVIGATE | ניווט בתוך /dashboard | |
STORE_SET | שמירת ערך | |
STORE_GET | קריאת ערך | |
STORE_DELETE | מחיקת ערך | |
STORE_LIST | רשימת מפתחות | |
FETCH_EXTERNAL | קריאת HTTP לשרת חיצוני | |
PRINT_HTML | הדפסת HTML | |
GENERATE_PDF | הפקת PDF מ-HTML | |
OPEN_NEW_WINDOW | פתיחת URL בטאב חדש | |
DOWNLOAD_FILE | הורדת קובץ | |
REPORT_USAGE | ספירת כניסות (אוטומטי) | ✅ |
ENTITY_CREATE | יצירת ישות | |
ENTITY_LIST | רשימת ישויות | |
ENTITY_UPDATE | עדכון ישות | |
ENTITY_DELETE | מחיקת ישות | |
ENTITY_COUNT | ספירת ישויות | |
GET_ACCESS_STATUS | בדיקת סטטוס גישה | |
SEND_EMAIL | שליחת מייל דרך SMTP | |
AI_COMPLETE | קריאה למודל AI | |
SCHEDULE_CREATE | יצירת משימה מתוזמנת | |
SCHEDULE_LIST | רשימת משימות מתוזמנות | |
SCHEDULE_DELETE | מחיקת משימה מתוזמנת |
טיפוס TypeScript: PermissionId — union של כל מזהי ההרשאות.
6. אחסון מפתח-ערך (Store)
מודל DB: ExtensionData
API Route: src/app/api/extension-bridge/store/route.ts
מודל Prisma
model ExtensionData {
id String @id @default(cuid())
extensionSlug String
companyId String
key String
value Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([extensionSlug, companyId, key])
@@index([extensionSlug, companyId])
}
פעולות
| פעולה | פקודה | תיאור |
|---|---|---|
| כתיבה | STORE_SET | upsert לפי slug + companyId + key |
| קריאה | STORE_GET | lookup — מחזיר value או null |
| מחיקה | STORE_DELETE | מחיקה שקטה (לא נכשל אם לא קיים) |
| רשימה | STORE_LIST | findMany עם אופציה ל-prefix |
בידוד נתונים
- כל הנתונים מבודדים לפי
extensionSlug + companyId. - Sandbox משתמש ב-
"__sandbox__"כ-companyId. - ערך נשמר כ-JSON (Prisma
Jsontype) — תומך בכל ערך serializable.
מתי להשתמש ב-Store (ולא Entity)
- הגדרות, מצב (state), cache, ערכים פשוטים.
- אין אכיפת מגבלות מצד השרת — ההרחבה אחראית להגביל אוספים בעצמה.
7. אחסון ישויות (Entity Storage)
מודל DB: ExtensionEntity
API Route: src/app/api/extension-bridge/entity/route.ts
מודל Prisma
model ExtensionEntity {
id String @id @default(cuid())
extensionSlug String
companyId String
entityType String
data Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([extensionSlug, companyId, entityType])
}
פעולות
| פעולה | פקודה | הערות |
|---|---|---|
| יצירה | ENTITY_CREATE | אוכף מגבלות (frozen/expired/limit) |
| רשימה | ENTITY_LIST | תומך ב-filter על שדות ב-data (in-memory) |
| עדכון | ENTITY_UPDATE | מחליף את כל ה-data |
| מחיקה | ENTITY_DELETE | בודק בעלות (slug + companyId) |
| ספירה | ENTITY_COUNT | ספירה פשוטה |
אכיפת מגבלות (ב-CREATE בלבד)
כש-limitMode === "entity" וה-entityType מתאים ל-limitEntityType של ההרחבה:
- בודק שהגישה פעילה (לא frozen, לא expired)
- סופר ישויות קיימות מאותו סוג
- משווה מול
entityLimitOverride ?? extension.limitValue - מחזיר
LIMIT_REACHEDאם חורג
מתי להשתמש ב-Entity (ולא Store)
- רשימות/אוספים: אנשי קשר, חשבוניות, משימות
- כשצריך ספירה אוטומטית ואכיפת מגבלות מצד השרת
- כשצריך סינון על שדות
השוואה — Store vs Entity
| תכונה | Store | Entity |
|---|---|---|
| מבנה | key-value חופשי | רשומות עם id, entityType, data |
| ספירה מובנית | ❌ | ✅ ENTITY_COUNT |
| אכיפת מגבלות | ❌ | ✅ בזמן ENTITY_CREATE |
| חיפוש/סינון | ❌ (רק prefix ב-LIST) | ✅ filter על שדות |
| שימוש מומלץ | הגדרות, state, cache | רשימות/אוספים |
8. גישה, רישוי ומגבלות
מודל Prisma — ExtensionAccess
model ExtensionAccess {
id String @id @default(cuid())
extensionId String
userId String? // הענקה ברמת משתמש
companyId String? // הענקה ברמת חברה
source String @default("free_auto")
isActive Boolean @default(true)
usageCount Int @default(0)
usageLimit Int? // override לכל הענקה
grantedAt DateTime @default(now())
expiresAt DateTime?
lastUsedAt DateTime?
frozenAt DateTime?
frozenReason String?
entityLimitOverride Int?
@@unique([extensionId, userId])
@@unique([extensionId, companyId])
}
סוגי מקור גישה (source)
| source | תיאור |
|---|---|
admin_grant | מנהל העניק ידנית |
public_auto | הרחבה ציבורית — גישה אוטומטית |
free_auto | הרחבה חינמית — נוצר אוטומטית |
היקף רישוי (licenseScope)
| ערך | תיאור |
|---|---|
"company" | (ברירת מחדל) גישה לפי חברה — כל עובדי החברה חולקים אותה הענקה |
"user" | גישה לפי משתמש — כל משתמש צריך הענקה נפרדת |
מצבי גישה
| מצב | תנאי |
|---|---|
| ACTIVE | isActive: true, לא frozen, לא expired |
| FROZEN | frozenAt מוגדר (פעולת אדמין) |
| EXPIRED | expiresAt < now |
| INACTIVE | isActive: false |
| NO_ACCESS | לא נמצאה הענקת גישה |
מצבי הגבלה (limitMode)
| limitMode | מנגנון ספירה | מה נאכף |
|---|---|---|
"usage" | אוטומטי — הפלטפורמה סופרת כל כניסה (פתיחת עמוד) | כניסה נחסמת כש-usageCount >= usageLimit |
"entity" | אוטומטי — ספירת ENTITY_CREATE | ENTITY_CREATE נכשל כש-count >= limit |
"none" | אין | אין מגבלה |
מערכת Trial
Extension.trialValue+Extension.trialUnit(hours/days/weeks/months/years)- בגישה ראשונה — מחושב
expiresAtונשמר בהענקה - כשאדמין משנה הגדרות trial — כל ההענקות הקיימות מתאפסות
לוגיקת גישה בזמן ריצה
משתמש נכנס ל-/dashboard/extensions/[slug]
│
├─ licenseScope === "company"?
│ └─ חפש ExtensionAccess לפי extensionId + companyId
│
├─ licenseScope === "user"?
│ └─ חפש לפי extensionId + userId → fallback לחברה
│
├─ SUPERADMIN / ADMIN_ASSISTANT?
│ └─ עוקף הכל — גישה מלאה
│
├─ isPublic === true בלי הענקה?
│ └─ יוצר הענקה אוטומטית
│
└─ limitMode === "usage"?
└─ מעלה usageCount ב-1 (רענון בתוך 60 שניות לא נספר)
9. שליחת מייל (SEND_EMAIL)
API Route: src/app/api/extension-bridge/send-email/route.ts
מגבלות
| פרמטר | ערך |
|---|---|
| נמענים לכל שליחה | עד 5 |
| מיילים ליום | עד 50 לכל הרחבה+חברה (מתאפס בחצות UTC) |
| אורך נושא | עד 200 תווים |
| גוף מייל | עד 60,000 תווים |
| FROM | קבוע — כתובת SMTP של הפלטפורמה (לא ניתן לשינוי) |
זרימה
הרחבה → SEND_EMAIL { to, subject, html/text, replyTo? }
│
├─ ולידציית פרמטרים (מייל, אורכים)
├─ בדיקת SMTP מוגדר
├─ בדיקת Rate Limit (ספירה ב-Store עם מפתח __email_daily_count__YYYY-MM-DD)
├─ שליחה דרך nodemailer
└─ עדכון מונה יומי
שגיאות אפשריות
SMTP_NOT_CONFIGURED, DAILY_LIMIT_REACHED, SEND_FAILED, INVALID_PAYLOAD
10. תזמון משימות (Scheduler)
API Route: src/app/api/extension-bridge/scheduler/route.ts
Executor: src/lib/scheduled-task-executor.ts
Cron: src/lib/cron-runner.ts (כל 60 שניות)
מודל Prisma — ScheduledTask
model ScheduledTask {
id String @id @default(cuid())
extensionId String
companyId String
userId String
action String // "SEND_EMAIL"
payload Json
triggerAt DateTime
ref String? // מפתח ייחודי למניעת כפילויות
status String @default("pending")
executedAt DateTime?
errorMessage String?
attempts Int @default(0)
createdAt DateTime @default(now())
@@unique([extensionId, companyId, ref])
@@index([status, triggerAt])
}
מגבלות
| קבוע | ערך |
|---|---|
MAX_PENDING_PER_EXT | 100 משימות ממתינות לכל הרחבה+חברה |
MAX_ATTEMPTS | 3 ניסיונות חוזרים לפני כשלון |
BATCH_SIZE | 50 משימות לכל tick |
CLEANUP_DAYS | 30 יום — משימות ישנות נמחקות אוטומטית |
טווח triggerAt | 5 דקות עד שנה קדימה |
פעולות נתמכות
כרגע: SEND_EMAIL בלבד.
סטטוסים
| status | תיאור |
|---|---|
pending | ממתינה לביצוע |
executed | בוצעה בהצלחה |
failed | נכשלה (אחרי 3 ניסיונות) |
זרימת ביצוע
instrumentation.ts
└─ scheduleCronCheck() → setInterval(tick, 60_000)
tick() (כל 60 שניות):
├─ ... (snapshot logic)
└─ executeScheduledTasks()
├─ מצא pending tasks עם triggerAt <= now, attempts < 3
├─ לכל משימה: הגדל attempts, בצע action
│ ├─ SEND_EMAIL → nodemailer
│ ├─ הצלחה → status = "executed"
│ └─ כישלון → attempts < 3? נשאר pending : status = "failed"
└─ ניקוי משימות ישנות (> 30 יום)
שגיאות Bridge
LIMIT_REACHED (100 pending), DUPLICATE_REF (409), NOT_FOUND, CANNOT_DELETE (רק pending ניתנות למחיקה)
דוגמה — תזמון תזכורת מייל
var when = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
bridge.send({
type: "SCHEDULE_CREATE",
actionType: "SEND_EMAIL",
triggerAt: when,
ref: "reminder-check-123",
payload: {
to: "user@example.com",
subject: "תזכורת: שיק מספר 123",
html: "<div dir='rtl'><h2>תזכורת</h2><p>שיק 123 מגיע מחר.</p></div>",
},
});
11. קריאה ל-AI (AI_COMPLETE)
API Route: src/app/api/extension-bridge/ai/route.ts
תנאים
- מנהל המערכת חייב להפעיל AI להרחבה דרך ממשק "הרחבות AI"
- נדרש
ANTHROPIC_API_KEYבסביבה
מגבלות
| קבוע | ערך |
|---|---|
| מודל | claude-haiku-4-5-20251001 |
| ברירת מחדל maxTokens | 1,024 |
| מקסימום מוחלט | 4,096 |
| cap אפקטיבי | min(requested, HARD_MAX, extension.aiMaxTokensPerRequest) |
הגדרות אדמין (/api/admin/extensions/[id]/ai-config)
| שדה | תיאור |
|---|---|
aiEnabled | הפעלת AI להרחבה |
aiMaxTokensPerRequest | מגבלת טוקנים לבקשה |
aiMonthlyTokenBudget | תקציב טוקנים חודשי |
מודל DB — AiUsageLog
model AiUsageLog {
id String @id @default(cuid())
extensionSlug String
companyId String
userId String?
model String @default("claude-haiku-4-5-20251001")
promptTokens Int @default(0)
completionTokens Int @default(0)
totalTokens Int @default(0)
durationMs Int?
success Boolean @default(true)
errorCode String?
createdAt DateTime @default(now())
}
שגיאות
AI_NOT_ENABLED, AI_NOT_CONFIGURED, AI_TOKEN_LIMIT_EXCEEDED, AI_BUDGET_EXCEEDED, AI_PROVIDER_ERROR
12. קריאות HTTP חיצוניות (FETCH_EXTERNAL)
API Route: src/app/api/extension-bridge/fetch/route.ts
הגנות אבטחה
| הגנה | פירוט |
|---|---|
| SSRF | חסימת localhost, טווחי IP פרטיים (10.x, 192.168.x, 172.16-31.x, 127.x, 169.254.x), IPv6 loopback/ULA |
| פרוטוקול | רק http: ו-https: |
| Methods | GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS |
| Headers חסומים | host, cookie, authorization, x-forwarded-for |
| Timeout | 6 שניות (AbortController) |
תגובה
- אם
Content-Type: application/json→ JSON - אחרת → מחרוזת טקסט
13. הדפסה, PDF והורדת קבצים
כולן מטופלות בצד הלקוח (בלי API call):
PRINT_HTML / GENERATE_PDF
מציגות overlay מסך מלא עם iframe ל-printHtml וכפתור הדפסה.
DOWNLOAD_FILE
bridge.send({ type: "DOWNLOAD_FILE", filename: "report.csv", content: csvContent, mimeType: "text/csv;charset=utf-8" });
- יוצר
Blobמ-content + mimeType - לקבצי CSV: מוסיף BOM (
\uFEFF) לתמיכה בעברית ב-Excel - ריהוט שם קובץ: תווים מסוכנים מוחלפים ב-
_, מקסימום 255 תווים
14. הגדרת Config ו-Extension Data
GET_MY_EXTENSION_CONFIG
- ההרחבה מצהירה על שדות config ב-
@extension-config(ראה סעיף 15) - אדמין מגדיר ערכים דרך ממשק גרפי → נשמר ב-
Extension.contractJson.runtimeConfig - בטעינת ההרחבה,
extractRuntimeConfig()שולף את ה-config - הרחבה שולחת
GET_MY_EXTENSION_CONFIG→ מקבלת{ config: { ... } }
GET_EXTENSION_DATA
- זמינה רק למפתחים (תפקיד
DEVELOPER) - שולפת את ה-
contractJsonהמלא של הרחבה לפי slug - שימוש: הרחבה שתלויה בנתוני הרחבה אחרת
15. הצהרות @extension-entities ו-@extension-config
@extension-entities (חובה)
קובץ parser: src/lib/parseExtensionEntities.ts
בלוק הערה בתחילת הקובץ (לפני window.MyExtension) שמצהיר על כל סוגי הישויות:
/* @extension-entities
[
{ "type": "debtor", "label": "חייב", "description": "ישות ליבה", "recommendLimit": true },
{ "type": "payment", "label": "תשלום", "description": "תשלום לחייב", "recommendLimit": false }
]
*/
| שדה | חובה | תיאור |
|---|---|---|
type | ✅ | מזהה הישות (אנגלית) — חייב להתאים ל-entityType ב-ENTITY_CREATE |
label | ✅ | שם בעברית |
description | ✅ | הסבר קצר |
recommendLimit | ✅ | true לישויות ליבה (מומלץ להגביל), false לנגזרות/אוטומטיות |
- גם
/* @extension-entities [] */חוקי אם אין ישויות. - ה-Sandbox מציג אזהרה אם הבלוק חסר.
- אדמין בוחר לפי איזו ישות
recommendLimit: trueלהגביל.
@extension-config (אופציונלי, מומלץ)
קובץ parser: src/lib/parseExtensionConfig.ts
/* @extension-config
[
{ "key": "maxItems", "type": "number", "default": 10, "label": "מקסימום פריטים" },
{ "key": "mode", "type": "string", "default": "basic","label": "מצב תצוגה" },
{ "key": "enabled", "type": "boolean", "default": true, "label": "פעיל" }
]
*/
| שדה | חובה | ערכים |
|---|---|---|
key | ✅ | מחרוזת באנגלית — חייבת להתאים למה שההרחבה קוראת מה-config |
type | ✅ | "number" / "string" / "boolean" |
default | ✅ | ערך ברירת מחדל (חייב להתאים לטיפוס) |
label | ✅ | תיאור בעברית |
- בלי הבלוק: אדמין עורך JSON ידנית (עדיין עובד).
- עם הבלוק: הפלטפורמה בונה ממשק גרפי אוטומטי לעריכת config.
16. מחזור חיי פיתוח — מפתח → חנות → משתמש
קבצים ראשיים
| קובץ | תפקיד |
|---|---|
src/app/dashboard/developer/sandbox/page.tsx | Sandbox מפתח |
src/app/dashboard/developer/submit/page.tsx | הגשת הרחבה |
src/app/dashboard/developer/submissions/page.tsx | סטטוס הגשות |
src/app/api/developer/submissions/route.ts | API הגשות |
src/app/api/developer/upload-bundle/route.ts | העלאת bundle |
src/app/admin/extensions/page.tsx | ניהול הרחבות (אדמין) |
סטטוסי הגשה (SubmissionStatus)
DRAFT ──► PENDING ──┬──► APPROVED ──► IN_STORE
├──► CHANGES_REQUESTED
└──► REJECTED
מחזור החיים
1. מפתח כותב bundle
│
2. בודק ב-Developer Sandbox
│ (URL או paste code ← LOAD_SCRIPT)
│
3. מגיש (Submit) → ExtensionSubmission (PENDING)
│
4. אדמין סוקר:
│ ├─ APPROVED → ניתן להעביר ל-IN_STORE
│ ├─ CHANGES_REQUESTED → חוזר למפתח
│ └─ REJECTED
│
5. (אופציונלי) בטא:
│ אדמין מקצה BetaTestAssignment → בודקים רצים ב-/beta/[assignmentId]
│
6. אדמין יוצר Extension בחנות ומקשר Submission פעיל
│
7. משתמשים מקבלים גישה:
│ ├─ isPublic → אוטומטי
│ ├─ admin_grant → ידני
│ └─ רכישה (בעתיד)
│
8. משתמש נכנס → /dashboard/extensions/[slug] → iframe → bridge
תנאי הגשה
slug: רק^[a-z0-9-]+$bundleUrlחייב להסתיים ב-.js- חובה: name, slug, shortDescription, description, bundleUrl, version
תפקידי מפתח
SystemRole.DEVELOPER— נדרש לגשת לפורטל מפתחיםUser.developerLicenseExpiry— תוקף רשיון מפתחCompany.developerPortalEnabled— הפעלת פורטל מפתחים לחברה
17. חנות ההרחבות וקטגוריות
קבצים
| קובץ | תפקיד |
|---|---|
src/app/store/page.tsx | עמוד חנות ציבורי |
src/app/store/[slug]/page.tsx | עמוד פרטי הרחבה |
src/components/store/ExtensionStoreView.tsx | קומפוננטת תצוגת חנות |
src/lib/storeVisibility.ts | מדיניות נראות |
מודלי DB
model ExtensionCategory {
id String @id @default(cuid())
slug String @unique
name String
description String?
imageUrl String?
}
model ExtensionTag {
id String @id @default(cuid())
slug String @unique
name String
description String?
}
- ההרחבה שומרת
category(string) ו-tags(String[]) ישירות על המודל — ללא FK. ExtensionCategoryו-ExtensionTagהם lookup tables לניהול באדמין.
תמחור
| שדה | ערכים |
|---|---|
pricingType | "paid", "free", "limited" |
paymentType | "one_time" / null |
hasLicenseTiers | boolean — האם יש שכבות רישוי |
licenseTiers | JSON — מבנה שכבות (LicenseTierBuilder) |
דירוגים
model ExtensionRating {
rating Int
comment String?
userId String
extensionId String
@@unique([userId, extensionId])
}
הסתרת הרחבות
model UserExtensionHidden {
userId String
extensionId String
@@unique([userId, extensionId])
}
18. פרומפט הפלטפורמה (Platform Prompt)
קובץ מרכזי: src/lib/buildPlatformPrompt.ts (~1,300 שורות)
API Route: src/app/api/developer/platform-prompt/route.ts
תפקיד
מייצר מסמך פרומפט בעברית שמשמש:
- מודלי AI — כשמפתחים מבקשים מ-AI לכתוב הרחבה
- מפתחים אנושיים — כ-specification מלא
מבנה
- טבלת גרסאות פלטפורמה
- מבנה חובה של
window.MyExtension - טבלת Bridge API — נבנית דינמית מ-
ALL_PERMISSIONSדרךpermissionsTable() - דוגמאות שימוש לכל פקודה
- קודי שגיאה
- מגבלת כניסות (usage/entity)
@extension-entities— מפרט +recommendLimitguidelines@extension-config— מפרט- SEND_EMAIL — מפרט מלא + דוגמאות
- AI_COMPLETE — מפרט מלא + דוגמאות
- SCHEDULE_* — מפרט מלא + דוגמאות
- Entity Storage vs STORE — guidelines
- GET_ACCESS_STATUS — מפרט + אינדיקטור שימוש
- RoleGuard — מנגנון הרשאות פנימי (ACL)
- תמיכה רב-לשונית (he + ar)
- פורמט תאריכים ומספרים
פונקציות
permissionsTable()— בונה markdown table מ-switch-case על כל permissionpermissionsEnum()— מחזיר"PERM1" | "PERM2" | ...לטיפוסיםbuildPlatformPrompt(info: PlatformInfo)— הפונקציה הראשית
19. מודלי בסיס נתונים — מפת Prisma
מודלים הקשורים להרחבות
Extension (הרחבה)
├─ ExtensionAccess[] (הענקות גישה)
├─ ExtensionEntity[] (ישויות - דרך slug)
├─ ExtensionData[] (Store key-value - דרך slug)
├─ ExtensionSubmission[] (הגשות מפתחים)
├─ ExtensionRating[] (דירוגים)
├─ ScheduledTask[] (משימות מתוזמנות)
├─ AiUsageLog[] (לוגי שימוש AI - דרך slug)
├─ BetaTestAssignment[] (דרך Submission)
└─ DeveloperContract[] (חוזי מפתחים)
ExtensionCategory (קטגוריות)
ExtensionTag (תגיות)
UserExtensionHidden (הסתרת הרחבות)
שדות עיקריים על Extension
| שדה | סוג | תיאור |
|---|---|---|
slug | String (unique) | מזהה ייחודי |
name | String | שם |
bundleUrl | String? | URL ל-bundle JS |
manifestJson | Json? | metadata שנשמר |
contractJson | Json? | נתוני חוזה + runtimeConfig |
isPublic | Boolean | ציבורית? |
isActive | Boolean | פעילה? |
licenseScope | String | "company" / "user" |
limitMode | String? | "none" / "usage" / "entity" |
limitValue | Int? | ערך מגבלה |
limitEntityType | String? | סוג ישות להגבלה |
pricingType | String? | "free" / "paid" / "limited" |
trialValue | Int? | ערך trial |
trialUnit | String? | יחידת trial |
aiEnabled | Boolean | AI מופעל? |
aiMaxTokensPerRequest | Int? | מגבלת AI |
aiMonthlyTokenBudget | Int? | תקציב AI |
category | String? | קטגוריה |
tags | String[] | תגיות |
bundleSize | Int? | גודל bundle בבתים |
נספח — API Routes מסכם
| נתיב | Method | תיאור |
|---|---|---|
/api/my-extensions | GET | רשימת ההרחבות הנגישות למשתמש |
/api/my-extensions/[slug] | GET | טעינת הרחבה (גישה + config + usage) |
/api/extension-bridge/store | POST | STORE_SET / GET / DELETE / LIST |
/api/extension-bridge/entity | POST | ENTITY_CREATE / LIST / UPDATE / DELETE / COUNT |
/api/extension-bridge/fetch | POST | FETCH_EXTERNAL (proxy) |
/api/extension-bridge/send-email | POST | SEND_EMAIL |
/api/extension-bridge/scheduler | POST | SCHEDULE_CREATE / LIST / DELETE |
/api/extension-bridge/ai | POST | AI_COMPLETE |
/api/extension-bridge/access-status | POST | GET_ACCESS_STATUS |
/api/extension-bridge/company-users | POST | GET_COMPANY_USERS |
/api/extensions/[slug]/report-usage | POST | REPORT_USAGE |
/api/developer/submissions | GET/POST | הגשות |
/api/developer/upload-bundle | POST | העלאת bundle |
/api/developer/extension-data | GET | GET_EXTENSION_DATA |
/api/developer/platform-prompt | GET | הפרומפט לפיתוח |
/api/admin/extensions | GET/POST | ניהול הרחבות (אדמין) |
/api/admin/extensions/[id] | PATCH | עדכון הרחבה |
/api/admin/extensions/[id]/access | GET/POST/DELETE | ניהול גישות |
/api/admin/extensions/[id]/ai-config | GET/PUT | הגדרות AI |
/api/admin/extensions/[id]/dictionary | GET | מילון תרגומים |