Ü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)
responseType?: string; // 'json', 'text', 'stream' oder 'binary' für Datei-Downloads
}
Rückgabewert (Standard):
{
status: number; // HTTP-Statuscode
headers: object; // Response-Header
json: any; // Response-Body als JSON geparst (undefined, wenn kein gültiges JSON)
text: string; // Response-Body als Text
}
Rückgabewert (mit responseType: "stream" oder "binary"):
{
status: number; // HTTP-Statuscode
headers: object; // Response-Header
buffer: ArrayBuffer; // Response-Body als ArrayBuffer
success: boolean; // true bei Erfolg
}
Die Response-Struktur hängt vom responseType ab. Standardmäßig erhältst du json- und text-Eigenschaften. Bei responseType: "stream" oder "binary" erhältst du stattdessen eine buffer-Eigenschaft — json und text sind in diesem Modus nicht verfügbar.
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: "stream", // oder 'binary'
};
const response = await ld.request(options);
// ArrayBuffer in Base64 konvertieren
const bytes = new Uint8Array(response.buffer);
const binaryString = String.fromCharCode(...bytes);
return {
files: {
fileName: "document.pdf",
mimeType: "application/pdf",
base64: btoa(binaryString),
},
};
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: "alice@example.com", age: 30 },
{ name: "Bob", email: "bob@example.com", age: 25 },
{ name: "Charlie", email: "charlie@example.com", 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: Der bereinigte Abfrage-String 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: Der bereinigte Abfrage-String 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.
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: { signature: string } — Objekt mit der Signatur im angegebenen Encoding
Beispiel: JWT-Signierung
const header = {
alg: "RS256",
typ: "JWT",
};
const payload = {
iss: "your-client-id",
sub: "user@example.com",
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 result = ld.signWithRS256(signingInput, data.auth.private_key, {
encoding: "base64",
});
const jwt = `${signingInput}.${result.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.
ld.wait()
Pausiere die Ausführung für eine bestimmte Anzahl von Millisekunden. Nützlich für Rate Limiting oder Retry-Logik.
Parameter:
ms: number; // Millisekunden zum Warten (0-30000)
Beispiel:
// Wiederholung mit Verzögerung
let retries = 3;
while (retries > 0) {
const response = await ld.request(options);
if (response.status === 429) {
await ld.wait(2000); // 2 Sekunden warten vor erneutem Versuch
retries--;
continue;
}
return response.json;
}
return { error: "Rate Limit nach Wiederholungen überschritten" };
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()
Ein minimaler Polyfill zur Konvertierung zwischen typisierten Array-Formaten. Akzeptiert ein einzelnes Argument (Array, Uint8Array oder ArrayBuffer) und gibt ein Uint8Array zurück.
Dies ist NICHT der vollständige Node.js Buffer. Es akzeptiert nur Array, Uint8Array oder ArrayBuffer — keine Strings. Buffer.from("hello") wird einen Fehler auslösen. Das zurückgegebene Uint8Array hat keine .toString("base64")-Methode. Verwende stattdessen btoa()/atob() für Base64-Encoding/Decoding.
Beispiel: ArrayBuffer von ld.request() konvertieren
const response = await ld.request({
method: "GET",
url: `https://api.example.com/files/${data.input.fileId}`,
headers: { Authorization: `Bearer ${data.auth.access_token}` },
responseType: "stream",
});
// ArrayBuffer in Uint8Array konvertieren
const bytes = Buffer.from(response.buffer);
Beispiel: Heruntergeladene Datei in Base64 encodieren
// Verwende btoa() für Base64-Encoding, da Buffer.toString() nicht verfügbar ist
const binaryString = String.fromCharCode(...new Uint8Array(response.buffer));
const base64Data = btoa(binaryString);
return {
files: {
fileName: "document.pdf",
mimeType: "application/pdf",
base64: base64Data,
},
};
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