tambahan v2
This commit is contained in:
387
gen.ts
Normal file
387
gen.ts
Normal 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
267
gen.txt
Normal 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
263
gen.txt2
Normal 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
35
icon.svg
Normal 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
2432
openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
70
package.json
70
package.json
@@ -3,9 +3,9 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"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",
|
"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": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -23,5 +23,69 @@
|
|||||||
"@types/ssh2": "^1.15.5",
|
"@types/ssh2": "^1.15.5",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.9.3"
|
"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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
19
src/credentials/WibuApi.credentials.ts
Normal file
19
src/credentials/WibuApi.credentials.ts
Normal 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 }
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
Wibu: require('./nodes/Wibu/Wibu.node.js').Wibu,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 |
667
src/wibu/MCP Server/MCPServerApi.node.ts
Normal file
667
src/wibu/MCP Server/MCPServerApi.node.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
425
src/wibu/aduan/AduanApi.node.ts
Normal file
425
src/wibu/aduan/AduanApi.node.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
425
src/wibu/apikey/ApikeyApi.node.ts
Normal file
425
src/wibu/apikey/ApikeyApi.node.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
304
src/wibu/auth/AuthApi.node.ts
Normal file
304
src/wibu/auth/AuthApi.node.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
425
src/wibu/credential/CredentialApi.node.ts
Normal file
425
src/wibu/credential/CredentialApi.node.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
1042
src/wibu/darmasaba/DarmasabaApi.node.ts
Normal file
1042
src/wibu/darmasaba/DarmasabaApi.node.ts
Normal file
File diff suppressed because it is too large
Load Diff
183
src/wibu/layanan/LayananApi.node.ts
Normal file
183
src/wibu/layanan/LayananApi.node.ts
Normal 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
1398
src/wibu/mcp/McpApi.node.ts
Normal file
File diff suppressed because it is too large
Load Diff
425
src/wibu/pelayanan/PelayananApi.node.ts
Normal file
425
src/wibu/pelayanan/PelayananApi.node.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
546
src/wibu/pengaduan/PengaduanApi.node.ts
Normal file
546
src/wibu/pengaduan/PengaduanApi.node.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
304
src/wibu/user/UserApi.node.ts
Normal file
304
src/wibu/user/UserApi.node.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user