Überblick
Wenn du benutzerdefinierten Code für Integrationen, Actions, Trigger oder Workflow-Code-Nodes schreibst, hast du Zugriff auf eine Reihe von integrierten Utility-Funktionen. Diese Funktionen laufen in einer sicheren, isolierten JavaScript-Ausführungsumgebung, die wesentliche Funktionen bereitstellt, ohne externe Bibliotheken zu benötigen.
Was ist die Sandbox?
Die Sandbox ist eine sichere, isolierte JavaScript-Ausführungsumgebung, die:
- Nicht vertrauenswürdigen Code sicher ausführt - Speicherbegrenzte und zeitgesteuerte Ausführung
- Wesentliche Utilities bereitstellt - HTTP-Anfragen, Datenkonvertierungen, Kryptographie
- Sicherheitsrisiken verhindert - Kein Dateisystemzugriff, keine gefährlichen Globals wie
eval oder process
- Keine Abhängigkeiten erfordert - Keine npm-Pakete oder externe Imports erforderlich
Benutzerdefinierter Integrationscode läuft in einer sicheren Sandbox-Umgebung.
Du kannst keine externen Bibliotheken installieren oder importieren (npm, pip, etc.) - nur die hier dokumentierten integrierten JavaScript/Node.js-APIs sind verfügbar.
Für erweiterte Verarbeitung (z.B. PDF-Parsing, Bildmanipulation) verwende externe APIs oder Services und rufe sie aus deinem Integrationscode auf.
Wo diese Utilities verfügbar sind
Die Sandbox-Utilities sind verfügbar in:
- Benutzerdefinierten Integrations-Actions - Code, der mit externen APIs interagiert
- Benutzerdefinierten Integrations-Triggern - Code, der auf Events überwacht
- Authentifizierungsflows - OAuth- und API-Key-Validierungscode
- Workflow-Code-Nodes - Benutzerdefiniertes JavaScript in Workflow-Automatisierungen
HTTP & Networking
ld.request()
Führe HTTP-Anfragen an externe APIs mit automatischer JSON-Verarbeitung und Fehler-Management aus.
Parameter:
{
method: string; // HTTP-Methode: 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'
url: string; // Vollständige URL für die Anfrage
headers?: object; // Request-Header
params?: object; // URL-Query-Parameter
body?: object | string; // Request-Body (wird automatisch stringifiziert, wenn Objekt)
timeout?: number; // Request-Timeout in Millisekunden
responseType?: string; // 'stream' oder 'binary' für Datei-Downloads
}
Rückgabewert:
{
status: number; // HTTP-Statuscode
headers: object; // Response-Header
json: any; // Response-Body als JSON geparst
text: string; // Response-Body als Text
buffer: Buffer; // Response-Body als Buffer (für Binärdaten)
}
Beispiel: GET-Anfrage
const options = {
method: "GET",
url: "https://api.example.com/users/123",
headers: {
Authorization: `Bearer ${data.auth.access_token}`,
Accept: "application/json",
},
};
const response = await ld.request(options);
return response.json;
Beispiel: POST-Anfrage mit Body
const options = {
method: "POST",
url: "https://api.example.com/tickets",
headers: {
Authorization: `Bearer ${data.auth.api_key}`,
"Content-Type": "application/json",
},
body: {
title: data.input.title,
description: data.input.description,
priority: "high",
},
};
const response = await ld.request(options);
return {
ticketId: response.json.id,
url: response.json.url,
};
Beispiel: Datei-Download
const options = {
method: "GET",
url: `https://api.example.com/files/${data.input.fileId}/download`,
headers: {
Authorization: `Bearer ${data.auth.access_token}`,
},
responseType: "binary", // oder 'stream'
};
const response = await ld.request(options);
return {
files: {
fileName: "document.pdf",
mimeType: "application/pdf",
base64: response.buffer.toString("base64"),
},
};
Beispiel: FormData-Upload
const formData = new FormData();
formData.append("file", data.input.file.base64, data.input.file.fileName);
formData.append("description", "Hochgeladen via Langdock");
const options = {
method: "POST",
url: "https://api.example.com/upload",
headers: {
Authorization: `Bearer ${data.auth.access_token}`,
},
body: formData,
};
const response = await ld.request(options);
return response.json;
Der body-Parameter wird automatisch stringifiziert, wenn du ein Objekt übergibst.
Für application/x-www-form-urlencoded Content-Type wird der Body automatisch in das entsprechende Format konvertiert.
ld.awsRequest()
Führe AWS SigV4-signierte Anfragen an AWS-Services wie S3, API Gateway oder benutzerdefinierte AWS-APIs aus.
Parameter:
{
method: string; // HTTP-Methode
url: string; // AWS-Service-URL
headers?: object; // Zusätzliche Header
body?: object | string; // Request-Body
region: string; // AWS-Region (z.B. 'us-east-1')
service: string; // AWS-Service-Name (z.B. 's3', 'execute-api')
credentials: { // AWS-Credentials
accessKeyId: string; // AWS-Access-Key-ID
secretAccessKey: string; // AWS-Secret-Access-Key
sessionToken?: string; // AWS-Session-Token (für temporäre Credentials)
}
}
Beispiel: S3-Datei-Upload
const options = {
method: "PUT",
url: `https://my-bucket.s3.us-east-1.amazonaws.com/${data.input.fileName}`,
headers: {
"Content-Type": data.input.mimeType,
},
body: Buffer.from(data.input.file.base64, "base64"),
region: "us-east-1",
service: "s3",
credentials: {
accessKeyId: data.auth.aws_access_key_id,
secretAccessKey: data.auth.aws_secret_access_key,
},
};
const response = await ld.awsRequest(options);
return {
success: true,
url: options.url,
};
ld.csv2parquet()
Konvertiere CSV-Text in das Parquet-Format mit optionaler Komprimierung und Array-Unterstützung.
Parameter:
{
csvText: string; // CSV-Daten als Text
compression?: string; // 'gzip', 'snappy', 'brotli', 'lz4', 'zstd' (Standard) oder 'uncompressed'
}
Rückgabewert: { base64: string, success: boolean }
Beispiel:
const csvText = `name,age,skills
Alice,30,"[Python,JavaScript]"
Bob,25,"[Java,Go]"`;
const result = await ld.csv2parquet(csvText, {
compression: "gzip",
});
return {
files: {
fileName: "data.parquet",
mimeType: "application/vnd.apache.parquet",
base64: result.base64,
},
};
CSV-Spalten, die Array-ähnliche Strings enthalten (z.B. "[Python,JavaScript]"), werden automatisch erkannt und in Parquet-List-Spalten konvertiert.
ld.parquet2csv()
Konvertiere das Parquet-Format in CSV-Text, wobei List-Spalten entsprechend behandelt werden.
Parameter:
base64Parquet: string; // Base64-kodierte Parquet-Datei
Rückgabewert: { base64: string, success: boolean }
Beispiel:
const parquetBase64 = data.input.parquetFile.base64;
const result = await ld.parquet2csv(parquetBase64);
return {
files: {
fileName: "data.csv",
mimeType: "text/csv",
base64: result.base64,
},
};
ld.arrow2parquet()
Konvertiere das Arrow-IPC-Stream-Format in Parquet.
Parameter:
{
buffer: Buffer; // Arrow-IPC-Stream-Buffer
compression?: string; // Komprimierungstyp (wie bei csv2parquet)
}
Rückgabewert: Base64-kodierte Parquet-Datei
Beispiel:
const arrowBuffer = Buffer.from(data.input.arrowFile.base64, "base64");
const parquetBase64 = await ld.arrow2parquet(arrowBuffer, {
compression: "snappy",
});
return {
files: {
fileName: "data.parquet",
mimeType: "application/vnd.apache.parquet",
base64: parquetBase64,
},
};
ld.json2csv()
Konvertiere JSON-Daten in das CSV-Format mithilfe der nodejs-polars-Bibliothek.
Parameter:
jsonData: Array<object>; // Array von Objekten zum Konvertieren
Rückgabewert: CSV-Text
Beispiel:
const users = [
{ name: "Alice", email: "[email protected]", age: 30 },
{ name: "Bob", email: "[email protected]", age: 25 },
{ name: "Charlie", email: "[email protected]", age: 35 },
];
const csvText = await ld.json2csv(users);
return {
files: {
fileName: "users.csv",
mimeType: "text/csv",
text: csvText,
},
};
Datenbank & SQL
ld.validateSqlQuery()
Validiere die SQL-Abfrage-Syntax, um sicherzustellen, dass sie nicht leer ist und eine einzelne Anweisung enthält.
Parameter:
query: string; // Zu validierende SQL-Abfrage
Rückgabewert: true wenn gültig, wirft sonst einen Fehler
Beispiel:
const query = data.input.sqlQuery;
try {
ld.validateSqlQuery(query);
// Abfrage ist gültig, fahre mit Ausführung fort
const response = await ld.request({
method: "POST",
url: "https://api.example.com/query",
headers: {
Authorization: `Bearer ${data.auth.access_token}`,
},
body: { query },
});
return response.json;
} catch (error) {
return {
error: `Ungültige SQL-Abfrage: ${error.message}`,
};
}
ld.ensureReadOnlySqlQuery()
Stelle sicher, dass eine SQL-Abfrage schreibgeschützt ist, indem du ihren Ausführungstyp überprüfst.
Parameter:
query: string; // Zu validierende SQL-Abfrage
Rückgabewert: true wenn schreibgeschützt, wirft sonst einen Fehler
Beispiel:
const query = data.input.sqlQuery;
try {
ld.ensureReadOnlySqlQuery(query);
// Abfrage ist schreibgeschützt, sicher auszuführen
const response = await ld.request({
method: "POST",
url: `${data.auth.database_url}/query`,
headers: {
Authorization: `Bearer ${data.auth.access_token}`,
},
body: { query },
});
return response.json.results;
} catch (error) {
return {
error: "Nur schreibgeschützte Abfragen (SELECT) sind erlaubt",
};
}
Diese Funktion überprüft den Abfrage-Ausführungstyp, um sicherzustellen, dass er nur LISTING oder INFORMATION ist.
Abfragen, die Daten ändern (INSERT, UPDATE, DELETE), werden abgelehnt.
Microsoft XMLA (Power BI)
ld.microsoftXMLA.connect()
Stelle eine Verbindungssession zum Microsoft Power BI XMLA-Endpunkt her.
Parameter:
{
accessToken: string; // Azure AD Access-Token mit Power BI API-Scope
datasetName: string; // Dataset-Name (nicht ID)
groupId?: string; // Optionale Workspace/Gruppen-ID (weglassen für Mein Arbeitsbereich)
}
Rückgabewert: Session-Objekt mit sessionId
Die Azure-Region wird automatisch aus den Power BI API-Antworten erkannt.
Du musst sie nicht manuell angeben.
Beispiel:
const session = await ld.microsoftXMLA.connect({
accessToken: data.auth.access_token,
datasetName: data.input.dataset_name,
groupId: data.auth.workspace_id, // Optional - weglassen für Mein Arbeitsbereich
});
// Speichere Session-ID für nachfolgende Abfragen
return {
sessionId: session.sessionId,
message: "Erfolgreich verbunden",
};
ld.microsoftXMLA.query()
Führe eine DAX- oder MDX-Abfrage gegen eine etablierte XMLA-Session aus.
Funktionssignatur:
ld.microsoftXMLA.query(session, query)
Parameter:
session (object): Session-Objekt von connect()
query (string): DAX- oder MDX-Abfrage
Rückgabewert: Abfrageergebnisse als Array von Objekten
Beispiel:
// Zuerst Verbindung herstellen
const session = await ld.microsoftXMLA.connect({
region: data.auth.azure_region,
workspaceId: data.auth.workspace_id,
datasetName: data.input.dataset_name,
accessToken: data.auth.access_token,
});
// Abfrage ausführen
const results = await ld.microsoftXMLA.query(session, `
EVALUATE
SUMMARIZE(
Sales,
Sales[ProductCategory],
"TotalSales", SUM(Sales[Amount])
)
`);
return {
data: results,
rowCount: results.length,
};
Die XMLA-Verbindung verwendet ein 6-Schritt-Protokoll, um Sessions mit Power BI-Datasets herzustellen.
Die Session wird automatisch für dich verwaltet.
Kryptographie
ld.signWithRS256()
Erstelle RSA-SHA256-digitale Signaturen, die häufig für JWT-Signierung und OAuth-Flows verwendet werden.
Funktionssignatur:
ld.signWithRS256(data, privateKey, options)
Parameter:
data (string): Zu signierende Daten
privateKey (string): PEM-formatierter RSA Private Key
options (object, optional):
encoding (string): Output-Encoding - 'base64' (Standard) oder 'hex'
Rückgabewert: Signatur-String im angegebenen Encoding
Beispiel: JWT-Signierung
const header = {
alg: "RS256",
typ: "JWT",
};
const payload = {
iss: "your-client-id",
sub: "[email protected]",
aud: "https://api.example.com",
exp: Math.floor(Date.now() / 1000) + 3600, // 1 Stunde
iat: Math.floor(Date.now() / 1000),
};
const encodedHeader = btoa(JSON.stringify(header));
const encodedPayload = btoa(JSON.stringify(payload));
const signingInput = `${encodedHeader}.${encodedPayload}`;
const signature = ld.signWithRS256(signingInput, data.auth.private_key, {
encoding: "base64",
});
const jwt = `${signingInput}.${signature}`;
// Verwende JWT für Authentifizierung
const response = await ld.request({
method: "POST",
url: "https://api.example.com/auth/token",
body: {
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion: jwt,
},
});
return response.json;
Der Private Key muss im PEM-Format vorliegen. Ungültige Keys werfen benutzerfreundliche Fehlermeldungen.
Gib niemals Private Keys in Logs oder Rückgabewerten preis.
Utility-Funktionen
ld.log()
Gib Debugging-Informationen in die Ausführungslogs aus, die unterhalb des “Test Action”-Buttons sichtbar sind.
Parameter:
...args: any[]; // Beliebige Anzahl von Werten zum Loggen
Beispiel:
ld.log("Starte Ticket-Erstellung");
ld.log("Input-Daten:", data.input);
const response = await ld.request(options);
ld.log("Response-Status:", response.status);
ld.log("Ticket erstellt:", response.json);
return response.json;
Verwende ld.log() großzügig während der Entwicklung, um deinen Integrationscode zu debuggen.
Logs werden automatisch bereinigt, um sensible Authentifizierungswerte zu verbergen.
atob() / btoa()
Base64-Encoding- und -Decoding-Funktionen, global ohne Imports verfügbar.
atob() - Dekodiere Base64-String zu binärem String
btoa() - Kodiere binären String zu Base64
Beispiel: Base64-URL-Dekodierung
function base64UrlDecode(base64Url) {
// Konvertiere base64url zu Standard-Base64
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
return atob(base64);
}
const token = data.input.jwt_token;
const [header, payload, signature] = token.split(".");
const decodedPayload = JSON.parse(base64UrlDecode(payload));
ld.log("Token-Payload:", decodedPayload);
return decodedPayload;
Beispiel: Basic Authentication
const credentials = btoa(`${data.auth.username}:${data.auth.password}`);
const response = await ld.request({
method: "GET",
url: "https://api.example.com/data",
headers: {
Authorization: `Basic ${credentials}`,
},
});
return response.json;
Buffer.from()
Erstelle Buffer-Objekte für die Verarbeitung von Binärdaten.
Beispiel: Binär zu Base64
const binaryData = Buffer.from(data.input.text, "utf-8");
const base64Data = binaryData.toString("base64");
return {
base64: base64Data,
};
Beispiel: Base64 zu Binär
const buffer = Buffer.from(data.input.file.base64, "base64");
const response = await ld.request({
method: "PUT",
url: "https://api.example.com/upload",
headers: {
"Content-Type": data.input.file.mimeType,
},
body: buffer,
});
return response.json;
Erstelle Multipart-Form-Daten für Datei-Uploads und komplexe Request-Bodies.
Beispiel: Datei-Upload mit Metadaten
const formData = new FormData();
formData.append("file", data.input.file.base64, data.input.file.fileName);
formData.append("title", data.input.title);
formData.append("category", data.input.category);
formData.append("tags", JSON.stringify(data.input.tags));
const response = await ld.request({
method: "POST",
url: "https://api.example.com/documents",
headers: {
Authorization: `Bearer ${data.auth.access_token}`,
},
body: formData,
});
return response.json;
Standard-JavaScript-APIs
Die Sandbox bietet auch Zugriff auf Standard-JavaScript-Built-ins:
JSON
JSON.stringify() - Konvertiere Objekte zu JSON-Strings
JSON.parse() - Parse JSON-Strings zu Objekten
Date
new Date() - Erstelle Date-Objekte
Date.now() - Erhalte aktuellen Timestamp
- Alle Standard-Date-Methoden
Math
Math.floor(), Math.ceil(), Math.round()
Math.random(), Math.max(), Math.min()
- Alle Standard-Math-Methoden
RegExp
new RegExp() - Erstelle reguläre Ausdrücke
- String-Regex-Methoden:
match(), replace(), test()
Array & Object
- Alle Standard-Array-Methoden:
map(), filter(), reduce(), etc.
- Alle Standard-Object-Methoden:
keys(), values(), entries(), etc.
Beispiel: Datentransformation
const users = data.input.users;
// Filtere aktive User
const activeUsers = users.filter((user) => user.status === "active");
// Transformiere in erforderliches Format
const formatted = activeUsers.map((user) => ({
id: user.id,
name: `${user.firstName} ${user.lastName}`,
email: user.email.toLowerCase(),
joinedDate: new Date(user.createdAt).toISOString().split("T")[0],
}));
// Sortiere nach Beitrittsdatum
formatted.sort((a, b) => new Date(b.joinedDate) - new Date(a.joinedDate));
return {
users: formatted,
total: formatted.length,
};
Best Practices
Fehlerbehandlung
Wickle API-Aufrufe immer in Try-Catch-Blöcke ein und gib hilfreiche Fehlermeldungen:
try {
const response = await ld.request(options);
return response.json;
} catch (error) {
ld.log("Fehlerdetails:", error.message, error.stack);
return {
error: `Fehler beim Abrufen der Daten: ${error.message}`,
};
}
Validiere User-Inputs, bevor du sie verwendest:
// Validiere erforderliche Felder
if (!data.input.email || !data.input.name) {
return {
error: "Fehlende erforderliche Felder: E-Mail und Name sind erforderlich",
};
}
// Validiere E-Mail-Format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(data.input.email)) {
return {
error: "Ungültiges E-Mail-Format",
};
}
// Validiere Dateityp
const allowedTypes = ["application/pdf", "image/jpeg", "image/png"];
if (data.input.file && !allowedTypes.includes(data.input.file.mimeType)) {
return {
error: `Nicht unterstützter Dateityp. Erlaubt: ${allowedTypes.join(", ")}`,
};
}
Minimiere API-Aufrufe
// Schlecht: Mehrere sequentielle Aufrufe
const user = await ld.request({ url: "/users/123" });
const posts = await ld.request({ url: `/users/123/posts` });
const comments = await ld.request({ url: `/users/123/comments` });
// Gut: Verwende Batch-Endpunkte, wenn verfügbar
const response = await ld.request({
url: "/users/123?include=posts,comments",
});
Verwende Paginierung
const allResults = [];
let page = 1;
const pageSize = 100;
while (true) {
const response = await ld.request({
url: "https://api.example.com/data",
params: {
page,
pageSize,
},
});
allResults.push(...response.json.items);
if (response.json.items.length < pageSize) {
break; // Keine weiteren Seiten
}
page++;
}
return allResults;
Sicherheitsüberlegungen
Niemals Secrets hartcodieren
// Schlecht: Hartcodierter API-Key
const apiKey = "sk_live_abc123";
// Gut: Verwende Authentifizierungsfelder
const apiKey = data.auth.api_key;
Bereinige User-Input
// Bereinige SQL-ähnlichen Input
const searchTerm = data.input.search.replace(/['"]/g, "");
// Validiere URL-Parameter
const itemId = data.input.itemId.replace(/[^a-zA-Z0-9-_]/g, "");
Behandle Rate Limits
try {
const response = await ld.request(options);
return response.json;
} catch (error) {
if (error.message.includes("429") || error.message.includes("rate limit")) {
return {
error:
"API-Rate-Limit überschritten. Bitte versuche es in ein paar Minuten erneut.",
};
}
throw error;
}
Häufige Fallstricke
1. Async/Await nicht richtig behandeln
// Schlecht: Promise nicht awaiten
const response = ld.request(options); // Gibt Promise zurück, nicht Response!
return response.json; // undefined
// Gut: Immer awaiten
const response = await ld.request(options);
return response.json;
2. Auf verschachtelte Properties ohne Prüfung zugreifen
// Schlecht: Kann werfen, wenn user oder address undefined ist
const city = response.json.user.address.city;
// Gut: Verwende Optional Chaining
const city = response.json?.user?.address?.city || "Unbekannt";
3. JSON-Strings nicht parsen
// Schlecht: Annahme, dass String bereits ein Objekt ist
const properties = data.input.properties; // String: '{"name":"value"}'
properties.name; // undefined
// Gut: Parse JSON-Strings
const properties = JSON.parse(data.input.properties);
properties.name; // "value"
4. Eingefrorene Objekte modifizieren
// Schlecht: Sandbox friert globale Objekte ein
Array.prototype.customMethod = () => {}; // Fehler!
// Gut: Erstelle neue Objekte
const customArray = [...originalArray];
Nächste Schritte