Zum Hauptinhalt springen

Ü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,
};

Datenformat-Konvertierungen

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,
  },
};

FormData

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}`,
  };
}

Input-Validierung

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(", ")}`,
  };
}

Performance-Tipps

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