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

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: "[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;

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