tambahan v2

This commit is contained in:
bipproduction
2025-11-07 10:41:05 +08:00
parent b6ac42c709
commit 4fcb80df3d
22 changed files with 9615 additions and 75 deletions

387
gen.ts Normal file
View File

@@ -0,0 +1,387 @@
// tools/generate-n8n-from-openapi.ts
// Final stable generator: OpenAPI -> n8n nodes (TypeScript)
// Usage: node ./tools/generate-n8n-from-openapi.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
import fs from "fs";
import path from "path";
const NAMESPACE = "wibu";
function pascalCase(str: string) {
return str
.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase())
.replace(/^\w/, (c) => c.toUpperCase());
}
function ensureDir(p: string) {
if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true });
}
function readJSON(p: string) {
return JSON.parse(fs.readFileSync(p, "utf8"));
}
function escapeForTemplate(s: string) {
return s.replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
}
function parsePathParams(url: string) {
const out: string[] = [];
const re = /{([^}]+)}/g;
let m: RegExpExecArray | null;
while ((m = re.exec(url))) out.push(m[1]);
return out;
}
function detectRequestBodyType(requestBody: any) {
if (!requestBody || !requestBody.content) return null;
if (requestBody.content["multipart/form-data"]) return "formdata";
if (requestBody.content["application/x-www-form-urlencoded"]) return "formurl";
if (requestBody.content["application/json"]) return "json";
const keys = Object.keys(requestBody.content || {});
return keys.length ? keys[0] : null;
}
function buildParamListFromOps(ops: any[]) {
const map: Record<string, any> = {};
for (const op of ops) {
const params = op.parameters || [];
for (const p of params) {
const key = `${p.in}_${p.name}`;
if (!map[key]) map[key] = p;
}
const inferred = parsePathParams(op.path);
for (const ip of inferred) {
const key = `path_${ip}`;
if (!map[key]) map[key] = { name: ip, in: "path", required: true };
}
}
return map;
}
function generateNode(tag: string, operations: any[], outDir: string) {
const baseFolder = path.join(outDir, NAMESPACE);
ensureDir(baseFolder);
const folder = path.join(baseFolder, tag);
ensureDir(folder);
const className = `${pascalCase(tag)}Api`;
const merged = buildParamListFromOps(operations);
const mergedProps = Object.keys(merged)
.map((k) => {
const p = merged[k];
const display = `${pascalCase(p.in ?? "param")} - ${p.name}${p.required ? " *" : ""}`;
const type = p.schema && p.schema.type === "integer" ? "number" : "string";
return `{
displayName: "${display.replace(/"/g, '\\"')}",
name: "${k}",
type: "${type}",
default: "${(p.example ?? "") as string}"
}`;
})
.join(",");
const opsOptions = operations
.map((o) => {
const name = (o.summary ?? o.operationId ?? o.path).toString().replace(/"/g, '\\"');
return `{
name: "${name}",
value: "${o.operationId}"
}`;
})
.join(",");
// Build switch cases
const switchCases = operations
.map((op) => {
const opId = op.operationId;
const requestBodyTypeDetected = detectRequestBodyType(op.requestBody) ?? "null";
const pathParams = parsePathParams(op.path);
const replacePathCode = pathParams
.map(
(pp) =>
`endpoint = endpoint.replace(new RegExp('\\\\{${pp}\\\\}','g'), encodeURIComponent(String(pathParamsValues["${pp}"] ?? "")));`
)
.join("\n ");
const paginationBlock = op["x-pagination"]
? `// pagination collector
const allItems: any[] = [];
if (Array.isArray(parsed)) allItems.push(...parsed);
else if (parsed && parsed.items && Array.isArray(parsed.items)) allItems.push(...parsed.items);
else allItems.push(parsed);
let nextUrl: string | null = (parsed && (parsed.next || (parsed.links && parsed.links.next))) || null;
const visited = new Set<string>();
while (nextUrl) {
if (visited.has(nextUrl)) break;
visited.add(nextUrl);
const r2 = await fetch(nextUrl).catch(() => null);
if (!r2) break;
let p2: any = null;
try { p2 = await r2.json(); } catch { p2 = await r2.text().catch(() => null); }
if (Array.isArray(p2)) allItems.push(...p2);
else if (p2 && p2.items) allItems.push(...p2.items);
else allItems.push(p2);
nextUrl = (p2 && (p2.next || (p2.links && p2.links.next))) || null;
}
returnData.push(...allItems);
`
: `returnData.push(parsed);`;
return `case "${opId}": {
// build endpoint template and replace path params
let endpoint = \`${escapeForTemplate(op.path)}\`;
const pathParamsValues: Record<string, any> = {};
${pathParams.map((pp) => `pathParamsValues["${pp}"] = paramsCollection["path_${pp}"] ?? undefined;`).join("\n ")}
${replacePathCode}
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\\\{'+(creds.apiKeyName ?? 'api_key')+'\\\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = \`Bearer \${creds.oauth2.accessToken}\`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "${requestBodyTypeDetected}";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "${op.method.toUpperCase()}";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(\`HTTP \${resp.status}: \${m}\`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
${paginationBlock}
break;
}`;
})
.join("\n\n");
const nodeContent = `import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
export class ${className} implements INodeType {
description: INodeTypeDescription = {
displayName: "${NAMESPACE} - ${pascalCase(tag)}",
name: "${pascalCase(tag)}",
icon: "file:../../../icon.svg",
group: ["transform"],
version: 1,
description: "Auto-generated node (stable generator)",
defaults: { name: "${NAMESPACE} - ${pascalCase(tag)}" },
inputs: ["main"],
outputs: ["main"],
credentials: [{ name: "wibuApi", required: true }],
properties: [
{
displayName: "Operation",
name: "operation",
type: "options",
options: [
${opsOptions}
],
default: "${operations[0].operationId}"
},
{
displayName: "Parameters (path / query / header)",
name: "params",
type: "collection",
placeholder: "Add Parameter",
default: {},
options: [
${mergedProps}
]
},
{ displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] },
{ displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] },
{ displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] },
{ displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" },
{ displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] }
]
};
async execute(this: IExecuteFunctions) {
const returnData: any[] = [];
const items = this.getInputData();
const creds = await this.getCredentials("wibuApi") as any;
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter("operation", i) as string;
const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {};
const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {};
const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {};
const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {};
switch (operation) {
${switchCases}
default:
throw new Error("Operation not implemented: " + operation);
}
}
return [returnData];
}
}
`;
const filePath = path.join(folder, `${className}.node.ts`);
fs.writeFileSync(filePath, nodeContent, "utf8");
return className;
}
function writeCredentials(outDir: string) {
const credFolder = path.join(outDir, "credentials");
ensureDir(credFolder);
const credFile = `import { ICredentialType, INodeProperties } from "n8n-workflow";
export class WibuApi implements ICredentialType {
name = "wibuApi";
displayName = "Wibu API";
properties: INodeProperties[] = [
{ displayName: "Base URL", name: "baseUrl", type: "string", default: "", placeholder: "https://api.example.com" },
{ displayName: "Authentication Type", name: "authType", type: "options", options: [ { name: "None", value: "none" }, { name: "API Key", value: "apiKey" }, { name: "OAuth2 (scaffold)", value: "oauth2" } ], default: "apiKey" },
{ displayName: "API Key Name", name: "apiKeyName", type: "string", default: "Authorization", displayOptions: { show: { authType: ["apiKey"] } } },
{ displayName: "API Key Value", name: "apiKeyValue", type: "string", default: "", typeOptions: { password: true }, displayOptions: { show: { authType: ["apiKey"] } } },
{ displayName: "API Key Placement", name: "apiKeyPlacement", type: "options", options: [ { name: "Header", value: "header" }, { name: "Query", value: "query" }, { name: "Path", value: "path" } ], default: "header", displayOptions: { show: { authType: ["apiKey"] } } },
{ displayName: "OAuth2 - Access Token (manual)", name: "oauth2", type: "collection", default: {}, placeholder: "OAuth2 tokens", displayOptions: { show: { authType: ["oauth2"] } }, options: [ { displayName: "Access Token", name: "accessToken", type: "string", default: "", typeOptions: { password: true } }, { displayName: "Refresh Token", name: "refreshToken", type: "string", default: "", typeOptions: { password: true } }, { displayName: "Expires At (ms)", name: "expiresAt", type: "number", default: 0 } ] },
{ displayName: "Additional Headers (JSON)", name: "headersJson", type: "json", default: {} },
{ displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 },
{ displayName: "Retries", name: "retry", type: "number", default: 1 },
{ displayName: "Follow Redirects", name: "followRedirect", type: "boolean", default: true }
];
}
`;
fs.writeFileSync(path.join(credFolder, "WibuApi.credentials.ts"), credFile, "utf8");
}
export function main() {
const projectRoot = process.cwd();
const openapiPath = path.join(projectRoot, "openapi.json");
const outDir = path.join(projectRoot, "src");
if (!fs.existsSync(openapiPath)) {
console.error("❌ openapi.json not found");
process.exit(1);
}
const spec = readJSON(openapiPath);
const tagOps: Record<string, any[]> = {};
for (const [pathKey, pathItem] of Object.entries(spec.paths)) {
for (const [method, op] of Object.entries<any>(pathItem as any)) {
const tags = op.tags ?? ["default"];
for (const t of tags) {
if (!tagOps[t]) tagOps[t] = [];
tagOps[t].push({ ...op, path: pathKey, method });
}
}
}
const generated: string[] = [];
for (const [tag, ops] of Object.entries(tagOps)) {
const node = generateNode(tag, ops, outDir);
generated.push(node);
console.log(`✅ Generated node: ${node}`);
}
writeCredentials(outDir);
console.log(`\n✅ All done. Generated ${generated.length} nodes + credentials.`);
}
if (require.main === module) {
main();
}

267
gen.txt Normal file
View File

@@ -0,0 +1,267 @@
// generator from open api json to n8n custom nodes
/* eslint-disable @typescript-eslint/no-explicit-any */
import fs from "fs";
import path from "path";
import { pascalCase } from "pascal-case";
import pkg from "./package.json";
const openApiPath = "./openapi.json";
const baseNodesPath = "./src/nodes";
const BASE_URL = process.env.API_BASE_URL || "https://your-api-domain.com";
if (!fs.existsSync(openApiPath)) {
console.error("❌ File openapi.json tidak ditemukan.");
process.exit(1);
}
const openApi = JSON.parse(fs.readFileSync(openApiPath, "utf8"));
const sanitizeVarName = (str: string) => str.replace(/[^a-zA-Z0-9_]/g, "_");
function mapTypeToN8n(type: string): string {
const typeMap: Record<string, string> = {
string: "string",
number: "number",
integer: "number",
boolean: "boolean",
array: "string",
object: "json",
};
return typeMap[type] || "string";
}
function buildProperties(schema: any, requiredFields: string[] = []): string {
if (!schema || !schema.properties) {
return `{
displayName: "Data",
name: "data",
type: "json",
default: "{}",
description: "Request body sebagai JSON",
}`;
}
return Object.entries(schema.properties)
.map(([key, prop]: [string, any]) => {
const type = mapTypeToN8n(prop.type || "string");
const isRequired = requiredFields.includes(key);
const description = prop.description || prop.error || key;
return `{
displayName: "${key.charAt(0).toUpperCase() + key.slice(1)}",
name: "${key}",
type: "${type}",
default: ${type === "number" ? "0" : '""'},
required: ${isRequired},
description: "${description.replace(/"/g, "'")}",
}`;
})
.join(",\n ");
}
if (!fs.existsSync(baseNodesPath)) {
fs.mkdirSync(baseNodesPath, { recursive: true });
}
pkg.n8n.nodes = [];
for (const [pathUrl, methods] of Object.entries<any>(openApi.paths)) {
for (const [method, spec] of Object.entries<any>(methods)) {
const tag = spec.tags?.[0] || "default";
const rawOperationId = spec.operationId || `${method}_${pathUrl}`;
const cleanOperationId = sanitizeVarName(rawOperationId);
const className = pascalCase(cleanOperationId);
const summary = spec.summary || cleanOperationId;
const description = (spec.description || summary).replace(/"/g, "'");
const schema = spec.requestBody?.content?.["application/json"]?.schema || null;
const requiredFields = schema?.required || [];
const tagFolder = path.join(baseNodesPath, tag);
if (!fs.existsSync(tagFolder)) fs.mkdirSync(tagFolder);
const nodeFolder = path.join(tagFolder, cleanOperationId);
if (!fs.existsSync(nodeFolder)) fs.mkdirSync(nodeFolder);
// ===== DESCRIPTION FILE =====
fs.writeFileSync(
path.join(nodeFolder, `${cleanOperationId}.description.ts`),
`import { INodeTypeDescription } from "n8n-workflow";
export const ${cleanOperationId}Description: INodeTypeDescription = {
displayName: "${summary}",
name: "${cleanOperationId.toLowerCase()}",
group: ["transform"],
version: 1,
subtitle: '=${tag}',
description: "${description}",
codex: {
categories: ["BIP"],
resources: {},
alias: ["bip", "custom", "${tag.toLowerCase()}"],
},
defaults: {
name: "${summary}",
},
inputs: ["main"],
outputs: ["main"],
credentials: [
{
name: "apiAuth",
required: false,
},
],
properties: [
${buildProperties(schema, requiredFields)}
],
};
`
);
// ===== NODE FILE =====
const hasBody = ["post", "put", "patch"].includes(method.toLowerCase());
// Ambil property names untuk di-hardcode
const propertyNames = schema?.properties
? Object.keys(schema.properties).map(k => `"${k}"`).join(", ")
: "";
const hasProperties = hasBody && propertyNames;
fs.writeFileSync(
path.join(nodeFolder, `${cleanOperationId}.node.ts`),
`import {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
NodeOperationError,
} from "n8n-workflow";
import { ${cleanOperationId}Description } from "./${cleanOperationId}.description";
export class ${className} implements INodeType {
description: INodeTypeDescription = ${cleanOperationId}Description;
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
try {
${hasProperties ? `
// Property names dari schema
const propertyNames = [${propertyNames}];
const body: Record<string, any> = {};
for (const propName of propertyNames) {
try {
const value = this.getNodeParameter(propName, i, "");
if (value !== "") {
body[propName] = value;
}
} catch (error) {
// Property tidak ada, skip
}
}` : hasBody ? `
// Ambil body dari parameter "data" (karena tidak ada properties)
const body = this.getNodeParameter("data", i, {}) as Record<string, any>;` : ""}
const response = await this.helpers.httpRequest({
method: "${method.toUpperCase()}",
url: \`${BASE_URL}${pathUrl}\`,
${hasBody ? "body," : ""}
json: true,
headers: {
"Content-Type": "application/json",
},
});
returnData.push({
json: response,
pairedItem: { item: i },
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (this.continueOnFail()) {
returnData.push({
json: { error: errorMessage },
pairedItem: { item: i },
});
continue;
}
throw new NodeOperationError(
this.getNode(),
\`Error executing ${summary}: \${errorMessage}\`,
{ itemIndex: i }
);
}
}
return [returnData];
}
}
// ✅ Export untuk n8n (harus match dengan filename)
module.exports = { ${cleanOperationId}: ${className} };
`
);
const distPath = `dist/nodes/${tag}/${cleanOperationId}/${cleanOperationId}.node.js`;
if (!pkg.n8n.nodes.includes(distPath)) {
pkg.n8n.nodes.push(distPath);
}
console.log(`✅ Generated: ${className} (${method.toUpperCase()} ${pathUrl})`);
}
}
// ===== GENERATE src/index.ts =====
const indexPath = path.join("./src", "index.ts");
const nodeExports: string[] = [];
const nodeImports: Record<string, string[]> = {}; // tag -> [cleanOperationId]
// Kumpulkan semua nodes berdasarkan tag
for (const [pathUrl, methods] of Object.entries<any>(openApi.paths)) {
for (const [method, spec] of Object.entries<any>(methods)) {
const tag = spec.tags?.[0] || "default";
const rawOperationId = spec.operationId || `${method}_${pathUrl}`;
const cleanOperationId = sanitizeVarName(rawOperationId);
const className = pascalCase(cleanOperationId);
if (!nodeImports[tag]) {
nodeImports[tag] = [];
}
nodeImports[tag].push(cleanOperationId);
nodeExports.push(
` ${className}: require('./nodes/${tag}/${cleanOperationId}/${cleanOperationId}.node.js').${cleanOperationId}`
);
}
}
const indexContent = `// Auto-generated index file
// Generated on: ${new Date().toISOString()}
module.exports = {
${nodeExports.join(",\n")}
};
`;
fs.writeFileSync(indexPath, indexContent);
// Update package.json
fs.writeFileSync("./package.json", JSON.stringify(pkg, null, 2));
console.log("\n✨ Semua node berhasil di-generate!");
console.log(`📦 Total nodes: ${pkg.n8n.nodes.length}`);
console.log(`📄 Generated: src/index.ts`);
console.log(`\n📊 Nodes per category:`);
for (const [tag, nodes] of Object.entries(nodeImports)) {
console.log(` ${tag}: ${nodes.length} nodes`);
}
console.log(`\n⚠ Setup yang perlu dilakukan:`);
console.log(`1. Set BASE_URL: export API_BASE_URL=https://your-api.com`);
console.log(`2. Run: npm run build`);
console.log(`3. Test: n8n start`);

263
gen.txt2 Normal file
View File

@@ -0,0 +1,263 @@
import fs from "fs";
import path from "path";
const NAMESPACE = "wibu";
// ---------------------------
// Helper Functions
// ---------------------------
function pascalCase(str: string) {
return str
.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase())
.replace(/^\w/, (c) => c.toUpperCase());
}
function buildPathWithParams(url: string) {
return url.replace(/{(.*?)}/g, (match, p1) => `\${encodeURIComponent(params.${p1})}`);
}
function detectRequestBody(requestBody: any) {
if (!requestBody || !requestBody.content) return null;
if (requestBody.content["application/json"]) return "json";
if (requestBody.content["application/x-www-form-urlencoded"]) return "form";
return null;
}
function buildProperties(parameters: any[], requestBodyType: string | null) {
const props: any[] = [];
for (const p of parameters || []) {
props.push({
displayName: `${pascalCase(p.in)} - ${p.name}`,
name: `${p.in}_${p.name}`,
type: "string",
default: "",
});
}
if (requestBodyType === "json") {
props.push({
displayName: "JSON Body",
name: "bodyJson",
type: "json",
default: {},
});
}
return props;
}
// ---------------------------
// Main Node Generator
// ---------------------------
function generateNode(tag: string, operations: any[], outDir: string) {
// Namespace folder: src/nodes/<namespace>
const baseFolder = path.join(outDir, NAMESPACE);
if (!fs.existsSync(baseFolder)) fs.mkdirSync(baseFolder, { recursive: true });
// Subfolder per-tag: src/nodes/<namespace>/<tag>
const folder = path.join(baseFolder, tag);
if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
const className = `${pascalCase(tag)}Api`;
// Create switch-case
const switchCases = operations
.map((op) => {
const requestBodyType = detectRequestBody(op.requestBody);
return `
case "${op.operationId}": {
const params = this.getNodeParameter("params", i, {}) as any;
const query = this.getNodeParameter("query", i, {}) as any;
const body = this.getNodeParameter("body", i, {}) as any;
const url = \`${buildPathWithParams(op.path)}\`;
const qs = query;
const options: any = {
method: "${op.method.toUpperCase()}",
uri: url,
qs,
json: true,
};
${requestBodyType === "json" ? `options.body = body.bodyJson;` : ""}
const response = await this.helpers.request(options);
returnData.push(response);
break;
}
`;
})
.join("\n");
// Build dynamic properties
const allProps = operations.flatMap((op: any) => {
const body = detectRequestBody(op.requestBody);
return buildProperties(op.parameters, body);
});
const nodeContent = `
import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
export class ${className} implements INodeType {
description: INodeTypeDescription = {
displayName: "${NAMESPACE} - ${pascalCase(tag)}",
name: "${pascalCase(tag)}",
group: ["transform"],
version: 1,
description: "Auto-generated from OpenAPI",
defaults: {
name: "${NAMESPACE} - ${pascalCase(tag)}",
},
icon: "file:../../../icon.svg",
inputs: ["main"],
outputs: ["main"],
properties: [
{
displayName: "Operation",
name: "operation",
type: "options",
options: [
${operations
.map(
(o) => `
{
name: "${o.summary || o.operationId}",
value: "${o.operationId}"
}
`
)
.join(",")}
],
default: "${operations[0].operationId}",
},
{
displayName: "Parameters",
name: "params",
type: "collection",
placeholder: "Add Parameter",
default: {},
options: [
${allProps
.map(
(p) => `
{
displayName: "${p.displayName}",
name: "${p.name}",
type: "string",
default: "",
}
`
)
.join(",")}
]
},
{
displayName: "Query",
name: "query",
type: "collection",
placeholder: "Add Query",
default: {},
options: []
},
{
displayName: "Body",
name: "body",
type: "collection",
default: {},
options: [
{
displayName: "JSON Body",
name: "bodyJson",
type: "json",
default: {}
}
]
}
]
};
async execute(this: IExecuteFunctions) {
const returnData: any[] = [];
const items = this.getInputData();
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter("operation", i) as string;
switch (operation) {
${switchCases}
default:
throw new Error("Operation not implemented: " + operation);
}
}
return [returnData];
}
}
`;
const file = path.join(folder, `${className}.node.ts`);
fs.writeFileSync(file, nodeContent);
return className;
}
// ---------------------------
// Main Program
// ---------------------------
export function main() {
const projectRoot = process.cwd();
const openapiPath = path.join(projectRoot, "openapi.json");
const outDir = path.join(projectRoot, "src", "nodes");
if (!fs.existsSync(openapiPath)) {
console.error("❌ openapi.json not found");
process.exit(1);
}
const openapi = JSON.parse(fs.readFileSync(openapiPath, "utf8"));
const tagsMap: any = {};
// Group by tags
for (const pathKey of Object.keys(openapi.paths)) {
const pathObj = openapi.paths[pathKey];
for (const method of Object.keys(pathObj)) {
const op = pathObj[method];
if (!op.tags) continue;
const item = {
path: pathKey,
method,
...op,
};
for (const t of op.tags) {
tagsMap[t] = tagsMap[t] || [];
tagsMap[t].push(item);
}
}
}
// Generate index.ts exports
const indexExports: string[] = [];
for (const tag of Object.keys(tagsMap)) {
const className = generateNode(tag, tagsMap[tag], outDir);
indexExports.push(`export * from "./nodes/${NAMESPACE}/${tag}/${className}.node";`);
}
const indexPath = path.join(projectRoot, "src", "index.ts");
fs.writeFileSync(indexPath, indexExports.join("\n"));
console.log("✅ Generation complete");
}
main();

35
icon.svg Normal file
View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 48 48" id="happy">
<symbol id="New_Symbol_14" viewBox="-6.5 -6.5 13 13">
<path fill="#FFD4C3" stroke="#504B46" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M0-6c2.2 0 4.1 1.5 4.7 3.5C6.3-2.5 6.4 0 5 0v1c0 2.8-2.2 5-5 5s-5-2.2-5-5V0c-1.4 0-1.3-2.5.2-2.5C-4.1-4.5-2.2-6 0-6z" style="fill:#ffd4c3;stroke:#504b46;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<circle cx="-1.6" cy="-.1" r=".1" style="fill:#ffc258"></circle>
<path d="M-1.6.5c-.3 0-.6-.3-.6-.6s.2-.7.6-.7c.3 0 .6.3.6.7s-.3.6-.6.6z" style="fill:#4f4b45"></path>
<circle cx="1.6" cy="-.1" r=".1" style="fill:#ffc258"></circle>
<path d="M1.6.5C1.3.5 1 .2 1-.1s.3-.6.6-.6.6.3.6.6-.2.6-.6.6z" style="fill:#4f4b45"></path>
<circle cx="-3" cy="-1.5" r=".5" style="fill:#fabfa5"></circle>
<circle cx="3" cy="-1.5" r=".5" style="fill:#fabfa5"></circle>
<path fill="none" stroke="#504B46" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M-1.2-3c.8-.5 1.7-.5 2.5 0" style="fill:none;stroke:#504b46;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
</symbol>
<g id="Icons">
<g id="XMLID_2_">
<circle id="XMLID_7676_" cx="20.8" cy="21.5" r="20" fill="#FFE500" style="fill:#ffe500"></circle>
<path id="XMLID_7673_" fill="#EBCB00" d="M20.8 1.5c-11 0-20 9-20 20s9 20 20 20 20-9 20-20-9-20-20-20zm0 37c-10.1 0-18.2-8.2-18.2-18.2C2.5 10.1 10.7 2 20.8 2S39 10.1 39 20.2s-8.2 18.3-18.2 18.3z" style="fill:#ebcb00"></path>
<ellipse id="XMLID_7672_" cx="20.8" cy="5.5" fill="#FFF48C" rx="6" ry="1.5" style="fill:#fff48c"></ellipse>
<ellipse id="XMLID_7671_" cx="20.8" cy="45" fill="#45413C" opacity=".15" rx="16" ry="1.5" style="opacity:.15;fill:#45413c"></ellipse>
<circle id="XMLID_7670_" cx="20.8" cy="21.5" r="20" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></circle>
<ellipse id="XMLID_7669_" cx="32.8" cy="26.5" rx="2.5" ry="1.5" style="fill:#ffaa54"></ellipse>
<ellipse id="XMLID_7668_" cx="8.7" cy="26.5" rx="2.5" ry="1.5" style="fill:#ffaa54"></ellipse>
<path id="XMLID_7667_" d="M6.8 22.7c0-1 .8-1.8 1.8-1.8s1.8.8 1.8 1.8" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7666_" d="M31.2 22.7c0-1 .8-1.8 1.8-1.8s1.8.8 1.8 1.8" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7665_" d="m42.4 30.6 4.5-1.5c.2-.1.3-.2.3-.4s-.1-.3-.3-.4l-3.7-1.2c-.5-.2-.9-.6-1.1-1.1l-1.2-3.7c-.1-.2-.2-.3-.4-.3s-.3.1-.4.3L38.9 26c-.2.5-.6.9-1.1 1.1L34 28.3c-.2.1-.3.2-.3.4s.1.3.3.4l3.7 1.2c.5.2.9.6 1.1 1.1l1.2 3.7c.1.2.2.3.4.3s.3-.1.4-.3l1.6-4.5z" style="stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;fill:#fff"></path>
<g id="XMLID_7658_">
<path id="XMLID_7664_" d="M20.8 36.5c9.2 0 10.4-3.8 9.9-7-.3-1.9-2.5-3.1-4.6-2.4-1.5.5-3.4.8-5.3.8s-3.8-.3-5.3-.8c-2-.7-4.2.5-4.6 2.4-.6 3.2.6 7 9.9 7z" style="stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;fill:#fff"></path>
<g id="XMLID_7659_">
<path id="XMLID_7663_" d="M30.7 30.2c-1 .5-2.5 1-4.6 1.4-1.5.2-3.2.4-5.4.4s-3.9-.2-5.4-.4c-2.1-.4-3.6-.9-4.6-1.4" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7662_" d="m16 27.4-.6 4.2-.7 4.1" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7661_" d="M20.8 36.5V28" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7660_" d="m25.5 27.4.6 4.2.7 4.1" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

2432
openapi.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,9 +3,9 @@
"version": "1.0.0",
"main": "dist/index.js",
"scripts": {
"build": "tsc && cp src/nodes/Wibu/logo.svg dist/nodes/Wibu/logo.svg",
"build": "rm -rf dist && tsc && cp icon.svg dist",
"prepare": "npm run build",
"test": "echo \"Error: no test specified\" && exit 1"
"gen": "sudo rm -rf src/nodes && bun gen.ts && npm run build && sudo rm -rf /Users/bip/documents/projects/docker/n8n/data/n8n/custom/n8n-nodes-wibu/dist && sudo cp -r /Users/bip/Documents/projects/docker/n8n-nodes-wibu /Users/bip/documents/projects/docker/n8n/data/n8n/custom/n8n-nodes-wibu && docker restart n8n"
},
"keywords": [],
"author": "",
@@ -23,5 +23,69 @@
"@types/ssh2": "^1.15.5",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
}
}
},
"n8n": {
"nodes": [
"dist/nodes/mcp/getApiPengaduanCategory/getApiPengaduanCategory.node.js",
"dist/nodes/pengaduan/postApiPengaduanCategoryCreate/postApiPengaduanCategoryCreate.node.js",
"dist/nodes/pengaduan/postApiPengaduanCategoryUpdate/postApiPengaduanCategoryUpdate.node.js",
"dist/nodes/pengaduan/postApiPengaduanCategoryDelete/postApiPengaduanCategoryDelete.node.js",
"dist/nodes/mcp/postApiPengaduanCreate/postApiPengaduanCreate.node.js",
"dist/nodes/pengaduan/postApiPengaduanUpdate_status/postApiPengaduanUpdate_status.node.js",
"dist/nodes/mcp/getApiPengaduanDetail/getApiPengaduanDetail.node.js",
"dist/nodes/mcp/getApiPengaduan/getApiPengaduan.node.js",
"dist/nodes/mcp/getApiPelayananCategory/getApiPelayananCategory.node.js",
"dist/nodes/pelayanan/postApiPelayananCategoryCreate/postApiPelayananCategoryCreate.node.js",
"dist/nodes/pelayanan/postApiPelayananCategoryUpdate/postApiPelayananCategoryUpdate.node.js",
"dist/nodes/pelayanan/postApiPelayananCategoryDelete/postApiPelayananCategoryDelete.node.js",
"dist/nodes/mcp/getApiPelayanan/getApiPelayanan.node.js",
"dist/nodes/mcp/getApiPelayananDetail/getApiPelayananDetail.node.js",
"dist/nodes/mcp/postApiPelayananCreate/postApiPelayananCreate.node.js",
"dist/nodes/mcp/postApiPelayananUpdate_status/postApiPelayananUpdate_status.node.js",
"dist/nodes/apikey/postApiApikeyCreate/postApiApikeyCreate.node.js",
"dist/nodes/apikey/getApiApikeyList/getApiApikeyList.node.js",
"dist/nodes/apikey/deleteApiApikeyDelete/deleteApiApikeyDelete.node.js",
"dist/nodes/darmasaba/getApiDarmasabaRepos/getApiDarmasabaRepos.node.js",
"dist/nodes/darmasaba/getApiDarmasabaLs/getApiDarmasabaLs.node.js",
"dist/nodes/darmasaba/getApiDarmasabaLsByDir/getApiDarmasabaLsByDir.node.js",
"dist/nodes/darmasaba/getApiDarmasabaFileByDirByFile_name/getApiDarmasabaFileByDirByFile_name.node.js",
"dist/nodes/darmasaba/getApiDarmasabaList_pengetahuan_umum/getApiDarmasabaList_pengetahuan_umum.node.js",
"dist/nodes/darmasaba/getApiDarmasabaPengetahuan_umumByFile_name/getApiDarmasabaPengetahuan_umumByFile_name.node.js",
"dist/nodes/darmasaba/postApiDarmasabaBuat_pengaduan/postApiDarmasabaBuat_pengaduan.node.js",
"dist/nodes/darmasaba/postApiDarmasabaStatus_pengaduan/postApiDarmasabaStatus_pengaduan.node.js",
"dist/nodes/credential/postApiCredentialCreate/postApiCredentialCreate.node.js",
"dist/nodes/credential/getApiCredentialList/getApiCredentialList.node.js",
"dist/nodes/credential/deleteApiCredentialRm/deleteApiCredentialRm.node.js",
"dist/nodes/user/getApiUserFind/getApiUserFind.node.js",
"dist/nodes/user/postApiUserUpsert/postApiUserUpsert.node.js",
"dist/nodes/layanan/getApiLayananList/getApiLayananList.node.js",
"dist/nodes/mcp/postApiLayananCreate_ktp/postApiLayananCreate_ktp.node.js",
"dist/nodes/mcp/postApiLayananStatus_ktp/postApiLayananStatus_ktp.node.js",
"dist/nodes/aduan/postApiAduanCreate/postApiAduanCreate.node.js",
"dist/nodes/aduan/postApiAduanAduan_sampah/postApiAduanAduan_sampah.node.js",
"dist/nodes/aduan/getApiAduanList_aduan_sampah/getApiAduanList_aduan_sampah.node.js",
"dist/nodes/auth/postAuthLogin/postAuthLogin.node.js",
"dist/nodes/auth/deleteAuthLogout/deleteAuthLogout.node.js",
"dist/nodes/MCP Server/postMcp/postMcp.node.js",
"dist/nodes/MCP Server/getMcpTools/getMcpTools.node.js",
"dist/nodes/MCP Server/getMcpStatus/getMcpStatus.node.js",
"dist/nodes/MCP Server/getHealth/getHealth.node.js",
"dist/nodes/MCP Server/getMcpInit/getMcpInit.node.js",
"dist/nodes/mcp/mcp.node.js",
"dist/nodes/pengaduan/pengaduan.node.js",
"dist/nodes/pelayanan/pelayanan.node.js",
"dist/nodes/apikey/apikey.node.js",
"dist/nodes/darmasaba/darmasaba.node.js",
"dist/nodes/credential/credential.node.js",
"dist/nodes/user/user.node.js",
"dist/nodes/layanan/layanan.node.js",
"dist/nodes/aduan/aduan.node.js",
"dist/nodes/auth/auth.node.js",
"dist/nodes/MCP_Server/MCP_Server.node.js",
"dist/nodes/mcp_server/mcp_server.node.js"
]
},
"files": [
"icon.svg"
]
}

View File

@@ -0,0 +1,19 @@
import { ICredentialType, INodeProperties } from "n8n-workflow";
export class WibuApi implements ICredentialType {
name = "wibuApi";
displayName = "Wibu API";
properties: INodeProperties[] = [
{ displayName: "Base URL", name: "baseUrl", type: "string", default: "", placeholder: "https://api.example.com" },
{ displayName: "Authentication Type", name: "authType", type: "options", options: [ { name: "None", value: "none" }, { name: "API Key", value: "apiKey" }, { name: "OAuth2 (scaffold)", value: "oauth2" } ], default: "apiKey" },
{ displayName: "API Key Name", name: "apiKeyName", type: "string", default: "Authorization", displayOptions: { show: { authType: ["apiKey"] } } },
{ displayName: "API Key Value", name: "apiKeyValue", type: "string", default: "", typeOptions: { password: true }, displayOptions: { show: { authType: ["apiKey"] } } },
{ displayName: "API Key Placement", name: "apiKeyPlacement", type: "options", options: [ { name: "Header", value: "header" }, { name: "Query", value: "query" }, { name: "Path", value: "path" } ], default: "header", displayOptions: { show: { authType: ["apiKey"] } } },
{ displayName: "OAuth2 - Access Token (manual)", name: "oauth2", type: "collection", default: {}, placeholder: "OAuth2 tokens", displayOptions: { show: { authType: ["oauth2"] } }, options: [ { displayName: "Access Token", name: "accessToken", type: "string", default: "", typeOptions: { password: true } }, { displayName: "Refresh Token", name: "refreshToken", type: "string", default: "", typeOptions: { password: true } }, { displayName: "Expires At (ms)", name: "expiresAt", type: "number", default: 0 } ] },
{ displayName: "Additional Headers (JSON)", name: "headersJson", type: "json", default: {} },
{ displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 },
{ displayName: "Retries", name: "retry", type: "number", default: 1 },
{ displayName: "Follow Redirects", name: "followRedirect", type: "boolean", default: true }
];
}

View File

@@ -1,3 +0,0 @@
module.exports = {
Wibu: require('./nodes/Wibu/Wibu.node.js').Wibu,
};

View File

@@ -1,20 +0,0 @@
{
"version": 1,
"description": "Node wibu untuk generate kata-kata anime",
"defaults": {
"name": "Wibu"
},
"inputs": ["main"],
"outputs": ["main"],
"credentials": [],
"properties": [
{
"displayName": "Text",
"name": "text",
"type": "string",
"default": "konnichiwa",
"placeholder": "Masukkan kata-kata wibu"
}
]
}

View File

@@ -1,46 +0,0 @@
import { IExecuteFunctions } from 'n8n-workflow';
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
export class Wibu implements INodeType {
description: INodeTypeDescription = {
displayName: 'Wibu',
name: 'wibu',
icon: 'file:logo.svg',
group: ['transform'],
version: 1,
description: 'Node wibu custom',
defaults: {
name: 'Wibu',
},
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Text',
name: 'text',
type: 'string',
default: '',
required: true,
},
],
};
async execute(this: IExecuteFunctions) {
const returnData = [];
const items = this.getInputData();
for (let i = 0; i < items.length; i++) {
const text = this.getNodeParameter('text', i) as string;
returnData.push({
json: {
hasil: `Halo wibu: ${text}`,
},
});
}
return [returnData];
}
}

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 128 128" data-name="Layer 1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#00adfe;}.cls-2{fill:#356cb6;opacity:0.3;}.cls-3,.cls-8{fill:#393c54;}.cls-4{fill:#ffffff;}.cls-5,.cls-6{fill:none;stroke:#393c54;stroke-linecap:round;stroke-miterlimit:10;}.cls-5{stroke-width:5.51px;}.cls-6{stroke-width:2.21px;}.cls-7{fill:#a7aece;}.cls-8{opacity:0.2;}</style></defs><title/><circle class="cls-1" cx="64" cy="64" r="60"/><circle class="cls-2" cx="64" cy="64" r="48"/><path class="cls-3" d="M64,124a59.8,59.8,0,0,0,41.54-16.72c-1-22.43-3.94-55.49-12.65-75.18C88.06,21.18,76.74,13.88,64,13.88h0c-12.74,0-24.65,7-28.89,18.22C27.58,51.93,24.35,85.33,23,107.76A59.74,59.74,0,0,0,64,124Z"/><path class="cls-4" d="M84.13,36.13c-3.52-8.48-10.48-12.82-19.74-13v0h-.78v0c-9.26.22-16.22,4.56-19.74,13-3.63,8.71-4.83,21.77,0,39.19,4.69,17,10.54,20.49,19.74,20.67h.78c9.2-.18,15-3.72,19.74-20.67C89,57.9,87.76,44.84,84.13,36.13Z"/><line class="cls-5" x1="77.58" x2="81.99" y1="52.83" y2="52.83"/><path class="cls-3" d="M68.5,88a30.85,30.85,0,0,1-9,0c-1.25-.33-2.5-1.12-2.5-2.5s1.2-2.13,2.5-2.5a20.4,20.4,0,0,1,9,0c1.21.31,2.5,1.12,2.5,2.5S69.73,87.68,68.5,88Z"/><path class="cls-6" d="M82.05,58.11a9.91,9.91,0,0,1-5.73-.37"/><path class="cls-7" d="M75.42,65A3.31,3.31,0,0,1,82,65c0,1.83-3.31,14.33-5.51,14.33C75.42,79.29,75.42,66.79,75.42,65Z"/><path class="cls-7" d="M75.37,45A3.31,3.31,0,0,0,82,45c0-1.82-2.59-9.92-4.41-9.92S75.37,43.19,75.37,45Z"/><line class="cls-5" x1="46.01" x2="50.42" y1="52.83" y2="52.83"/><path class="cls-6" d="M51.68,57.76a10,10,0,0,1-5.91.29"/><path class="cls-7" d="M52.63,45A3.31,3.31,0,0,1,46,45c0-1.82,2.59-9.92,4.41-9.92S52.63,43.19,52.63,45Z"/><path class="cls-7" d="M52.58,65A3.31,3.31,0,0,0,46,65c0,1.83,3.31,14.33,5.51,14.33C52.58,79.29,52.58,66.79,52.58,65Z"/><path class="cls-7" d="M68.41,90.32c0,1.22-2,2.21-4.41,2.21s-4.41-1-4.41-2.21c0-.62,2-.27,4.41-.27S68.41,89.7,68.41,90.32Z"/><path class="cls-8" d="M84.13,36.13a21.8,21.8,0,0,0-4.48-6.94c1.45,10.32.63,23.64-1.36,41.86-2.09,19.1-11.43,23.3-21,24a21.16,21.16,0,0,0,6.35.94h.78c9.2-.18,15-3.72,19.74-20.67C89,57.9,87.76,44.84,84.13,36.13Z"/></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,667 @@
import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
export class MCPServerApi implements INodeType {
description: INodeTypeDescription = {
displayName: "wibu - MCPServer",
name: "MCPServer",
icon: "file:../../../icon.svg",
group: ["transform"],
version: 1,
description: "Auto-generated node (stable generator)",
defaults: { name: "wibu - MCPServer" },
inputs: ["main"],
outputs: ["main"],
credentials: [{ name: "wibuApi", required: true }],
properties: [
{
displayName: "Operation",
name: "operation",
type: "options",
options: [
{
name: "postMcp",
value: "postMcp"
},{
name: "getMcpTools",
value: "getMcpTools"
},{
name: "getMcpStatus",
value: "getMcpStatus"
},{
name: "getHealth",
value: "getHealth"
},{
name: "getMcpInit",
value: "getMcpInit"
}
],
default: "postMcp"
},
{
displayName: "Parameters (path / query / header)",
name: "params",
type: "collection",
placeholder: "Add Parameter",
default: {},
options: [
]
},
{ displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] },
{ displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] },
{ displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] },
{ displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" },
{ displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] }
]
};
async execute(this: IExecuteFunctions) {
const returnData: any[] = [];
const items = this.getInputData();
const creds = await this.getCredentials("wibuApi") as any;
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter("operation", i) as string;
const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {};
const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {};
const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {};
const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {};
switch (operation) {
case "postMcp": {
// build endpoint template and replace path params
let endpoint = `/mcp`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "null";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "getMcpTools": {
// build endpoint template and replace path params
let endpoint = `/mcp/tools`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "null";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "GET";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "getMcpStatus": {
// build endpoint template and replace path params
let endpoint = `/mcp/status`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "null";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "GET";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "getHealth": {
// build endpoint template and replace path params
let endpoint = `/health`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "null";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "GET";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "getMcpInit": {
// build endpoint template and replace path params
let endpoint = `/mcp/init`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "null";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "GET";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
default:
throw new Error("Operation not implemented: " + operation);
}
}
return [returnData];
}
}

View File

@@ -0,0 +1,425 @@
import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
export class AduanApi implements INodeType {
description: INodeTypeDescription = {
displayName: "wibu - Aduan",
name: "Aduan",
icon: "file:../../../icon.svg",
group: ["transform"],
version: 1,
description: "Auto-generated node (stable generator)",
defaults: { name: "wibu - Aduan" },
inputs: ["main"],
outputs: ["main"],
credentials: [{ name: "wibuApi", required: true }],
properties: [
{
displayName: "Operation",
name: "operation",
type: "options",
options: [
{
name: "create",
value: "postApiAduanCreate"
},{
name: "aduan sampah",
value: "postApiAduanAduan-sampah"
},{
name: "list aduan sampah",
value: "getApiAduanList-aduan-sampah"
}
],
default: "postApiAduanCreate"
},
{
displayName: "Parameters (path / query / header)",
name: "params",
type: "collection",
placeholder: "Add Parameter",
default: {},
options: [
]
},
{ displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] },
{ displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] },
{ displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] },
{ displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" },
{ displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] }
]
};
async execute(this: IExecuteFunctions) {
const returnData: any[] = [];
const items = this.getInputData();
const creds = await this.getCredentials("wibuApi") as any;
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter("operation", i) as string;
const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {};
const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {};
const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {};
const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {};
switch (operation) {
case "postApiAduanCreate": {
// build endpoint template and replace path params
let endpoint = `/api/aduan/create`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "postApiAduanAduan-sampah": {
// build endpoint template and replace path params
let endpoint = `/api/aduan/aduan-sampah`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "getApiAduanList-aduan-sampah": {
// build endpoint template and replace path params
let endpoint = `/api/aduan/list-aduan-sampah`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "null";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "GET";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
default:
throw new Error("Operation not implemented: " + operation);
}
}
return [returnData];
}
}

View File

@@ -0,0 +1,425 @@
import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
export class ApikeyApi implements INodeType {
description: INodeTypeDescription = {
displayName: "wibu - Apikey",
name: "Apikey",
icon: "file:../../../icon.svg",
group: ["transform"],
version: 1,
description: "Auto-generated node (stable generator)",
defaults: { name: "wibu - Apikey" },
inputs: ["main"],
outputs: ["main"],
credentials: [{ name: "wibuApi", required: true }],
properties: [
{
displayName: "Operation",
name: "operation",
type: "options",
options: [
{
name: "create",
value: "postApiApikeyCreate"
},{
name: "list",
value: "getApiApikeyList"
},{
name: "delete",
value: "deleteApiApikeyDelete"
}
],
default: "postApiApikeyCreate"
},
{
displayName: "Parameters (path / query / header)",
name: "params",
type: "collection",
placeholder: "Add Parameter",
default: {},
options: [
]
},
{ displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] },
{ displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] },
{ displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] },
{ displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" },
{ displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] }
]
};
async execute(this: IExecuteFunctions) {
const returnData: any[] = [];
const items = this.getInputData();
const creds = await this.getCredentials("wibuApi") as any;
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter("operation", i) as string;
const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {};
const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {};
const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {};
const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {};
switch (operation) {
case "postApiApikeyCreate": {
// build endpoint template and replace path params
let endpoint = `/api/apikey/create`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "getApiApikeyList": {
// build endpoint template and replace path params
let endpoint = `/api/apikey/list`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "null";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "GET";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "deleteApiApikeyDelete": {
// build endpoint template and replace path params
let endpoint = `/api/apikey/delete`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "DELETE";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
default:
throw new Error("Operation not implemented: " + operation);
}
}
return [returnData];
}
}

View File

@@ -0,0 +1,304 @@
import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
export class AuthApi implements INodeType {
description: INodeTypeDescription = {
displayName: "wibu - Auth",
name: "Auth",
icon: "file:../../../icon.svg",
group: ["transform"],
version: 1,
description: "Auto-generated node (stable generator)",
defaults: { name: "wibu - Auth" },
inputs: ["main"],
outputs: ["main"],
credentials: [{ name: "wibuApi", required: true }],
properties: [
{
displayName: "Operation",
name: "operation",
type: "options",
options: [
{
name: "login",
value: "postAuthLogin"
},{
name: "logout",
value: "deleteAuthLogout"
}
],
default: "postAuthLogin"
},
{
displayName: "Parameters (path / query / header)",
name: "params",
type: "collection",
placeholder: "Add Parameter",
default: {},
options: [
]
},
{ displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] },
{ displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] },
{ displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] },
{ displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" },
{ displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] }
]
};
async execute(this: IExecuteFunctions) {
const returnData: any[] = [];
const items = this.getInputData();
const creds = await this.getCredentials("wibuApi") as any;
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter("operation", i) as string;
const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {};
const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {};
const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {};
const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {};
switch (operation) {
case "postAuthLogin": {
// build endpoint template and replace path params
let endpoint = `/auth/login`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "deleteAuthLogout": {
// build endpoint template and replace path params
let endpoint = `/auth/logout`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "null";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "DELETE";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
default:
throw new Error("Operation not implemented: " + operation);
}
}
return [returnData];
}
}

View File

@@ -0,0 +1,425 @@
import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
export class CredentialApi implements INodeType {
description: INodeTypeDescription = {
displayName: "wibu - Credential",
name: "Credential",
icon: "file:../../../icon.svg",
group: ["transform"],
version: 1,
description: "Auto-generated node (stable generator)",
defaults: { name: "wibu - Credential" },
inputs: ["main"],
outputs: ["main"],
credentials: [{ name: "wibuApi", required: true }],
properties: [
{
displayName: "Operation",
name: "operation",
type: "options",
options: [
{
name: "create",
value: "postApiCredentialCreate"
},{
name: "list",
value: "getApiCredentialList"
},{
name: "rm",
value: "deleteApiCredentialRm"
}
],
default: "postApiCredentialCreate"
},
{
displayName: "Parameters (path / query / header)",
name: "params",
type: "collection",
placeholder: "Add Parameter",
default: {},
options: [
]
},
{ displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] },
{ displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] },
{ displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] },
{ displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" },
{ displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] }
]
};
async execute(this: IExecuteFunctions) {
const returnData: any[] = [];
const items = this.getInputData();
const creds = await this.getCredentials("wibuApi") as any;
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter("operation", i) as string;
const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {};
const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {};
const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {};
const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {};
switch (operation) {
case "postApiCredentialCreate": {
// build endpoint template and replace path params
let endpoint = `/api/credential/create`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "getApiCredentialList": {
// build endpoint template and replace path params
let endpoint = `/api/credential/list`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "null";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "GET";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "deleteApiCredentialRm": {
// build endpoint template and replace path params
let endpoint = `/api/credential/rm`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "DELETE";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
default:
throw new Error("Operation not implemented: " + operation);
}
}
return [returnData];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,183 @@
import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
export class LayananApi implements INodeType {
description: INodeTypeDescription = {
displayName: "wibu - Layanan",
name: "Layanan",
icon: "file:../../../icon.svg",
group: ["transform"],
version: 1,
description: "Auto-generated node (stable generator)",
defaults: { name: "wibu - Layanan" },
inputs: ["main"],
outputs: ["main"],
credentials: [{ name: "wibuApi", required: true }],
properties: [
{
displayName: "Operation",
name: "operation",
type: "options",
options: [
{
name: "List Layanan",
value: "getApiLayananList"
}
],
default: "getApiLayananList"
},
{
displayName: "Parameters (path / query / header)",
name: "params",
type: "collection",
placeholder: "Add Parameter",
default: {},
options: [
]
},
{ displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] },
{ displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] },
{ displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] },
{ displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" },
{ displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] }
]
};
async execute(this: IExecuteFunctions) {
const returnData: any[] = [];
const items = this.getInputData();
const creds = await this.getCredentials("wibuApi") as any;
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter("operation", i) as string;
const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {};
const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {};
const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {};
const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {};
switch (operation) {
case "getApiLayananList": {
// build endpoint template and replace path params
let endpoint = `/api/layanan/list`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "null";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "GET";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
default:
throw new Error("Operation not implemented: " + operation);
}
}
return [returnData];
}
}

1398
src/wibu/mcp/McpApi.node.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,425 @@
import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
export class PelayananApi implements INodeType {
description: INodeTypeDescription = {
displayName: "wibu - Pelayanan",
name: "Pelayanan",
icon: "file:../../../icon.svg",
group: ["transform"],
version: 1,
description: "Auto-generated node (stable generator)",
defaults: { name: "wibu - Pelayanan" },
inputs: ["main"],
outputs: ["main"],
credentials: [{ name: "wibuApi", required: true }],
properties: [
{
displayName: "Operation",
name: "operation",
type: "options",
options: [
{
name: "buat kategori pelayanan surat",
value: "postApiPelayananCategoryCreate"
},{
name: "update kategori pelayanan surat",
value: "postApiPelayananCategoryUpdate"
},{
name: "delete kategori pelayanan surat",
value: "postApiPelayananCategoryDelete"
}
],
default: "postApiPelayananCategoryCreate"
},
{
displayName: "Parameters (path / query / header)",
name: "params",
type: "collection",
placeholder: "Add Parameter",
default: {},
options: [
]
},
{ displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] },
{ displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] },
{ displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] },
{ displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" },
{ displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] }
]
};
async execute(this: IExecuteFunctions) {
const returnData: any[] = [];
const items = this.getInputData();
const creds = await this.getCredentials("wibuApi") as any;
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter("operation", i) as string;
const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {};
const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {};
const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {};
const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {};
switch (operation) {
case "postApiPelayananCategoryCreate": {
// build endpoint template and replace path params
let endpoint = `/api/pelayanan/category/create`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "postApiPelayananCategoryUpdate": {
// build endpoint template and replace path params
let endpoint = `/api/pelayanan/category/update`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "postApiPelayananCategoryDelete": {
// build endpoint template and replace path params
let endpoint = `/api/pelayanan/category/delete`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
default:
throw new Error("Operation not implemented: " + operation);
}
}
return [returnData];
}
}

View File

@@ -0,0 +1,546 @@
import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
export class PengaduanApi implements INodeType {
description: INodeTypeDescription = {
displayName: "wibu - Pengaduan",
name: "Pengaduan",
icon: "file:../../../icon.svg",
group: ["transform"],
version: 1,
description: "Auto-generated node (stable generator)",
defaults: { name: "wibu - Pengaduan" },
inputs: ["main"],
outputs: ["main"],
credentials: [{ name: "wibuApi", required: true }],
properties: [
{
displayName: "Operation",
name: "operation",
type: "options",
options: [
{
name: "buat kategori pengaduan",
value: "postApiPengaduanCategoryCreate"
},{
name: "update kategori pengaduan",
value: "postApiPengaduanCategoryUpdate"
},{
name: "delete kategori pengaduan",
value: "postApiPengaduanCategoryDelete"
},{
name: "Update status pengaduan",
value: "postApiPengaduanUpdate-status"
}
],
default: "postApiPengaduanCategoryCreate"
},
{
displayName: "Parameters (path / query / header)",
name: "params",
type: "collection",
placeholder: "Add Parameter",
default: {},
options: [
]
},
{ displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] },
{ displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] },
{ displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] },
{ displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" },
{ displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] }
]
};
async execute(this: IExecuteFunctions) {
const returnData: any[] = [];
const items = this.getInputData();
const creds = await this.getCredentials("wibuApi") as any;
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter("operation", i) as string;
const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {};
const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {};
const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {};
const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {};
switch (operation) {
case "postApiPengaduanCategoryCreate": {
// build endpoint template and replace path params
let endpoint = `/api/pengaduan/category/create`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "postApiPengaduanCategoryUpdate": {
// build endpoint template and replace path params
let endpoint = `/api/pengaduan/category/update`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "postApiPengaduanCategoryDelete": {
// build endpoint template and replace path params
let endpoint = `/api/pengaduan/category/delete`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "postApiPengaduanUpdate-status": {
// build endpoint template and replace path params
let endpoint = `/api/pengaduan/update-status`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
default:
throw new Error("Operation not implemented: " + operation);
}
}
return [returnData];
}
}

View File

@@ -0,0 +1,304 @@
import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
export class UserApi implements INodeType {
description: INodeTypeDescription = {
displayName: "wibu - User",
name: "User",
icon: "file:../../../icon.svg",
group: ["transform"],
version: 1,
description: "Auto-generated node (stable generator)",
defaults: { name: "wibu - User" },
inputs: ["main"],
outputs: ["main"],
credentials: [{ name: "wibuApi", required: true }],
properties: [
{
displayName: "Operation",
name: "operation",
type: "options",
options: [
{
name: "find",
value: "getApiUserFind"
},{
name: "upsert",
value: "postApiUserUpsert"
}
],
default: "getApiUserFind"
},
{
displayName: "Parameters (path / query / header)",
name: "params",
type: "collection",
placeholder: "Add Parameter",
default: {},
options: [
]
},
{ displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] },
{ displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] },
{ displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] },
{ displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" },
{ displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] }
]
};
async execute(this: IExecuteFunctions) {
const returnData: any[] = [];
const items = this.getInputData();
const creds = await this.getCredentials("wibuApi") as any;
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter("operation", i) as string;
const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {};
const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {};
const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {};
const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {};
switch (operation) {
case "getApiUserFind": {
// build endpoint template and replace path params
let endpoint = `/api/user/find`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "null";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "GET";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
case "postApiUserUpsert": {
// build endpoint template and replace path params
let endpoint = `/api/user/upsert`;
const pathParamsValues: Record<string, any> = {};
// build query string
const urlSearch = new URLSearchParams();
for (const k of Object.keys(queryCollection || {})) {
const v = queryCollection[k];
if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v));
}
// apiKey placement
if (creds && creds.authType === "apiKey" && creds.apiKeyValue) {
const placement = creds.apiKeyPlacement ?? "header";
if (placement === "header") {
headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue;
} else if (placement === "query") {
urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue);
} else if (placement === "path") {
endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue));
}
}
let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint;
const qs = urlSearch.toString();
if (qs) url += (url.includes('?') ? '&' : '?') + qs;
// build headers
const headers: Record<string, string> = {};
for (const hk of Object.keys(headerCollection || {})) {
const hv = headerCollection[hk];
if (hv !== undefined && hv !== null) headers[hk] = String(hv);
}
if (creds && creds.headersJson) {
try { Object.assign(headers, creds.headersJson); } catch (e) {}
}
if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) {
headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`;
}
// build body according to requestBodyType
let bodyToSend: any = undefined;
const requestBodyType: string = "formdata";
if (requestBodyType === "formdata") {
const FD: any = (globalThis as any).FormData ?? null;
if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill.");
const fd = new FD();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
fd.append(k, v);
}
bodyToSend = fd;
} else if (requestBodyType === "formurl") {
const urlp = new URLSearchParams();
for (const k of Object.keys(bodyCollection || {})) {
const v = bodyCollection[k];
if (v === undefined || v === null) continue;
urlp.append(k, String(v));
}
bodyToSend = urlp.toString();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (requestBodyType === "json") {
try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; }
} else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) {
bodyToSend = bodyCollection.rawString;
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
}
const method = "POST";
const init: any = { method, headers };
if (bodyToSend !== undefined) init.body = bodyToSend;
const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000);
const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1);
const GLOBAL_FOLLOW = creds && creds.followRedirect !== false;
const fetchWithTimeout = async (input: string, initOpt: any) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT);
try {
const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal });
clearTimeout(id);
return r;
} catch (err) {
clearTimeout(id);
throw err;
}
};
let resp: Response | null = null;
for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) {
try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); }
}
if (!resp) throw new Error('No response from fetch.');
const contentType = resp.headers.get('content-type') || '';
let parsed: any = null;
try {
if (contentType.includes('application/json')) parsed = await resp.json();
else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text();
else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; }
} catch (e) { parsed = await resp.text().catch(()=>null); }
if (!resp.ok) {
const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
const err: any = new Error(`HTTP ${resp.status}: ${m}`);
err.statusCode = resp.status; err.response = parsed; throw err;
}
returnData.push(parsed);
break;
}
default:
throw new Error("Operation not implemented: " + operation);
}
}
return [returnData];
}
}