Externe Apps & SDK
Eine externe App ist deine eigene Web-App – HTML, React, Next.js, egal – unter deiner eigenen Adresse. Methacore lädt sie im Dashboard in einem Iframe und in der Mitglieder-App in einer WebView. Mit @methacore/sdk meldet sie sich an, spricht die APIs, nutzt den App-Speicher und exportiert Dateien.
Gehostete Apps aus dem Studio brauchen das SDK nicht. Es ist ausschließlich für externe Apps gedacht.
Voraussetzungen
| Was | Regel |
|---|---|
| Adresse | nur https://, mit gültigem Zertifikat |
| Domain | der Host der Adresse muss zu den autorisierten Domains des DevHub-Projekts gehören |
| Einbetten | deine Seite muss sich im Dashboard (https://admin.methacore.de) einbetten lassen – kein X-Frame-Options: DENY, und falls du frame-ancestors setzt, muss das Dashboard darin stehen |
| Eintrag | im DevHub unter Methacore-App: Art Extern, die Adresse unter „Externe Adresse“ |
| OAuth-Client | ein Client der API „Methacore Apps“ mit den Scopes deiner App |
Installieren
npm install @methacore/sdk
Das Paket gibt es als ES-Modul und CommonJS, mit Typen. Einen globalen Build (<script>) gibt es nicht; nimm einen Bundler.
Einstieg
import { createMethacore } from "@methacore/sdk";
export const mc = createMethacore({
clientId: "cl_7d2e41a9",
scopes: ["mc.me.profile:read", "mc.me.events:read"],
});
const profil = await mc.api.apps.me.profile();
const { items: termine } = await mc.api.apps.me.events();
createMethacore erkennt selbst, wo die App läuft:
| Modus | Wann | Woher das Token kommt |
|---|---|---|
| Host | im Iframe des Dashboards oder in der WebView der Mitglieder-App | vom Host über die Bridge. Der Host führt die Zustimmung durch – du baust keinen Login. |
| Eigenständig | direkt im Browser unter deiner Adresse | vom SDK selbst: OAuth mit PKCE gegen den DevHub |
mc.inHost() sagt dir, welcher Modus gilt.
Im Host
Im Host fragt das SDK den Host per postMessage nach Kontext und Token. Nichts davon musst du selbst anstoßen:
import { createMethacore, host } from "@methacore/sdk";
const mc = createMethacore({ clientId: "cl_7d2e41a9", scopes: ["mc.me.groups:read"] });
if (mc.inHost()) {
const context = await host.context();
// { organisation: { id, name }, user: { id, firstName }, theme, locale, target, endpoints }
document.documentElement.dataset.theme = context.theme;
host.on("theme", ({ theme }) => (document.documentElement.dataset.theme = theme));
}
const gruppen = await mc.api.apps.me.groups();
await host.toast(`${gruppen.items.length} Gruppen geladen`, "green");
| Aufruf | Bedeutung |
|---|---|
host.context() | Verein, Mitglied, Thema, Sprache, Plattform und die Adressen (endpoints) |
host.token(scopes?) | ein Access-Token, bei Bedarf mit Zustimmung |
host.toast(message, color?) | Hinweis im Host |
host.navigate(path) | zu einer Seite des Hosts wechseln |
host.download({ filename, mime, base64 }) | Datei herunterladen bzw. teilen |
host.close() | App schließen |
host.on("theme", handler) | Themenwechsel des Hosts |
Die Bridge ist dasselbe Protokoll, das auch gehostete Apps sprechen: Nachrichten { mc: 1, id, type, payload }, Antworten { mc: 1, id, ok, result | error }. Ohne Antwort bricht eine Anfrage nach 15 Sekunden ab.
Das Iframe im Dashboard erlaubt externen Apps Skripte, Formulare, Downloads, Pop-ups und die eigene Origin – localStorage und Cookies deiner Domain funktionieren also. In der Mitglieder-App gilt, was eine WebView erlaubt.
Eigenständig: Anmeldung mit PKCE
Öffnet jemand deine App direkt, meldet sie sich selbst an:
import { createMethacore, AuthError } from "@methacore/sdk";
const mc = createMethacore({
clientId: "cl_7d2e41a9",
scopes: ["mc.me.profile:read"],
redirectUri: "https://app.example.de/callback",
});
// auf /callback
if (location.pathname === "/callback") {
await mc.auth.handleRedirect(); // prüft state, tauscht code + verifier gegen Tokens
history.replaceState(null, "", "/");
}
try {
const profil = await mc.api.apps.me.profile();
} catch (error) {
if (error instanceof AuthError && error.code === "login_required") await mc.auth.login();
else throw error;
}
| Methode | Bedeutung |
|---|---|
auth.login() | leitet zur Zustimmung des DevHub weiter |
auth.beginLogin() | liefert nur die Autorisierungs-URL, z. B. für einen eigenen Knopf |
auth.handleRedirect(url?) | verarbeitet die Rückkehr, speichert die Tokens |
auth.getToken() | gültiges Access-Token, erneuert es mit dem Refresh-Token |
auth.logout() | vergisst die Tokens |
Tokens liegen standardmäßig im localStorage unter mc.sdk.token.<clientId>; eigene Ablage über die Option storage. Der Client ist öffentlich – es gibt kein Client-Secret im Browser.
Redirect-URIs
- Im Host brauchst du keine: der Host meldet sich über
https://apps-run.methacore.de/oauth/callbackan, und diese Adresse lässt der DevHub für jeden Client der API „Methacore Apps“ zu. - Eigenständig muss die Redirect-URI auf einer autorisierten Domain des Projekts liegen und
httpssprechen. Für die Entwicklung isthttp://localhostbzw.http://*.localhosterlaubt.
Ohne redirectUri nimmt das SDK die aktuelle Seite (Origin und Pfad).
Lokal entwickeln
import { configure } from "@methacore/sdk";
configure({
authBase: "http://localhost:8000", // DevHub-API
apiBase: "http://{section}.api.localhost:8080/v1", // {section} wird ersetzt, z. B. apps
redirectUri: "http://localhost:3000/callback",
});
Im Host gelten immer die Adressen, die der Host im Kontext mitschickt; configure wirkt nur eigenständig.
Scopes nachfordern
Antwortet die API mit 403 und does not carry the <scope> scope, holt das SDK den Scope nach:
- im Host einmal über
auth.tokenmitreauthorize: true– der Host zeigt „Zugriff erweitern“ – und wiederholt den Aufruf einmal, - eigenständig mit einer neuen Anmeldung, die den zusätzlichen Scope verlangt (die Seite wird dabei verlassen).
API-Client
await mc.api.apps.me.profile(); // Profile
await mc.api.apps.me.groups(); // { items: Group[] }
await mc.api.apps.me.events(); // { items: EventItem[] }
await mc.api.apps.me.trainingplans();
await mc.api.apps.me.competitions();
await mc.api.apps.me.membercard(); // { card: … | null }
await mc.api.apps.storage.list("notizen", { scope: "user", limit: 50 });
await mc.api.apps.storage.put("notizen", "n-1", { text: "Hallo" });
// jede andere Methacore-API
await mc.api.request("members", "/members", { query: { limit: 20 } });
Fehler kommen als ApiError mit status, message und dem Antwortkörper. Welche Endpunkte es gibt, steht unter Entwickler-API, der Speicher unter App-Speicher.
Exporte
await mc.exportRows(rows, {
format: "xlsx", // "csv" | "xlsx" | "pdf"
filename: "teilnehmer",
columns: [{ key: "name", label: "Name" }, { key: "gruppe", label: "Gruppe" }],
});
Im Host geht die Datei über die Bridge (Dashboard: Download, Handy: Teilen), eigenständig als normaler Browser-Download. xlsx und pdf-lib lädt das SDK erst beim ersten Export. Einzeln gibt es mc.export.toCsv, toXlsx und toPdf.
Die Warnung beim ersten Öffnen
Externe Apps prüft Methacore nicht. Deshalb sieht jedes Mitglied beim ersten Öffnen einen Hinweis mit dem Anbieter (Name deines DevHub-Teams) und einem Link zur Datenschutzerklärung – beides aus deinem DevHub-Projekt. Danach folgt die Zustimmung zu den Berechtigungen; erst dann lädt der Host deine Seite. Die Bestätigung merkt sich Methacore je Mitglied.
Im App Store trägt eine externe App den Hinweis „Extern – nicht von Methacore geprüft“.
Täglicher Check
Einmal am Tag ruft Methacore die Adresse deiner App auf. Antwortet sie nicht über https mit gültigem Zertifikat, gilt die App als nicht erreichbar, und der Store zeigt sie so an, bis der nächste Check gelingt. Adressen, die auf private oder interne Netze zeigen, bestehen den Check nie.
Checkliste
- Adresse mit
https, gültiges Zertifikat, Domain autorisiert - Einbetten durch
https://admin.methacore.deerlaubt - Client der API „Methacore Apps“, Scopes so eng wie möglich
- Datenschutzerklärung und Nutzungsbedingungen im DevHub hinterlegt
- Im Host getestet (Dashboard und, falls
targetmobile/both, Handy) und eigenständig