@ondc/automation-mock-runner 1.3.43 → 1.3.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/MockRunner.d.ts +11 -2
- package/dist/lib/MockRunner.js +29 -2
- package/dist/lib/configHelper.js +21 -64
- package/dist/lib/constants/function-registry.js +1 -1
- package/dist/lib/helpers/default-helpers-source.d.ts +1 -0
- package/dist/lib/helpers/default-helpers-source.js +221 -0
- package/dist/lib/helpers/default-helpers.d.ts +83 -0
- package/dist/lib/helpers/default-helpers.js +200 -0
- package/dist/lib/helpers/default-helpers.test.d.ts +9 -0
- package/dist/lib/helpers/default-helpers.test.js +265 -0
- package/dist/lib/helpers/index.d.ts +1 -0
- package/dist/lib/helpers/index.js +10 -0
- package/dist/lib/runners/node-runner.d.ts +15 -6
- package/dist/lib/runners/node-runner.js +4 -0
- package/dist/lib/runners/runner-factory.d.ts +3 -2
- package/dist/lib/validators/code-validator.js +3 -1
- package/dist/test/GenerateFetchAndTimeout.test.d.ts +4 -0
- package/dist/test/GenerateFetchAndTimeout.test.js +240 -0
- package/package.json +5 -2
- package/public/node-worker.js +98 -6
package/dist/lib/MockRunner.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseCodeRunner } from "./runners/base-runner";
|
|
2
|
+
import { RunnerOptions } from "./runners/runner-factory";
|
|
2
3
|
import { MockPlaygroundConfigType } from "./types/mock-config";
|
|
3
4
|
import { Logger } from "./utils/logger";
|
|
4
5
|
import { ExecutionResult } from "./types/execution-results";
|
|
@@ -7,6 +8,14 @@ export declare class MockRunner {
|
|
|
7
8
|
private static sharedRunner;
|
|
8
9
|
logger: Logger;
|
|
9
10
|
private static getSharedRunner;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize (or replace) the process-wide shared runner with explicit
|
|
13
|
+
* options. Call this once at service boot — before constructing any
|
|
14
|
+
* MockRunner — to configure the fetch allowlist and worker pool settings.
|
|
15
|
+
*
|
|
16
|
+
* If a shared runner already exists it is terminated and replaced.
|
|
17
|
+
*/
|
|
18
|
+
static initSharedRunner(options?: RunnerOptions): BaseCodeRunner;
|
|
10
19
|
constructor(config: MockPlaygroundConfigType, skipValidation?: boolean);
|
|
11
20
|
getRunnerInstance(): BaseCodeRunner;
|
|
12
21
|
getConfig(): {
|
|
@@ -69,9 +78,9 @@ export declare class MockRunner {
|
|
|
69
78
|
success: boolean;
|
|
70
79
|
errors?: import("zod/v4/core").$ZodIssue[];
|
|
71
80
|
};
|
|
72
|
-
runGeneratePayload(actionId: string, inputs?: any): Promise<ExecutionResult>;
|
|
81
|
+
runGeneratePayload(actionId: string, inputs?: any, extraSessionData?: Record<string, any>): Promise<ExecutionResult>;
|
|
73
82
|
runGeneratePayloadWithSession(actionId: string, sessionData: any): Promise<ExecutionResult>;
|
|
74
|
-
runValidatePayload(actionId: string, targetPayload: any): Promise<ExecutionResult>;
|
|
83
|
+
runValidatePayload(actionId: string, targetPayload: any, extraSessionData?: Record<string, any>): Promise<ExecutionResult>;
|
|
75
84
|
runValidatePayloadWithSession(actionId: string, targetPayload: any, sessionData: any): Promise<ExecutionResult>;
|
|
76
85
|
runMeetRequirements(actionId: string): Promise<ExecutionResult>;
|
|
77
86
|
runMeetRequirementsWithSession(actionId: string, sessionData: any): Promise<ExecutionResult>;
|
package/dist/lib/MockRunner.js
CHANGED
|
@@ -18,6 +18,27 @@ class MockRunner {
|
|
|
18
18
|
}
|
|
19
19
|
return MockRunner.sharedRunner;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Initialize (or replace) the process-wide shared runner with explicit
|
|
23
|
+
* options. Call this once at service boot — before constructing any
|
|
24
|
+
* MockRunner — to configure the fetch allowlist and worker pool settings.
|
|
25
|
+
*
|
|
26
|
+
* If a shared runner already exists it is terminated and replaced.
|
|
27
|
+
*/
|
|
28
|
+
static initSharedRunner(options = {}) {
|
|
29
|
+
const logger = logger_1.Logger.getInstance();
|
|
30
|
+
if (MockRunner.sharedRunner) {
|
|
31
|
+
logger.warn("Replacing existing shared runner");
|
|
32
|
+
try {
|
|
33
|
+
MockRunner.sharedRunner.terminate();
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
logger.error("Failed to terminate previous shared runner", {}, e);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
MockRunner.sharedRunner = runner_factory_1.RunnerFactory.createRunner(options, logger);
|
|
40
|
+
return MockRunner.sharedRunner;
|
|
41
|
+
}
|
|
21
42
|
constructor(config, skipValidation = false) {
|
|
22
43
|
this.logger = logger_1.Logger.getInstance();
|
|
23
44
|
if (!skipValidation) {
|
|
@@ -55,7 +76,7 @@ class MockRunner {
|
|
|
55
76
|
const res = (0, validateConfig_1.validateConfigWithErrors)(this.config);
|
|
56
77
|
return res;
|
|
57
78
|
}
|
|
58
|
-
async runGeneratePayload(actionId, inputs = {}) {
|
|
79
|
+
async runGeneratePayload(actionId, inputs = {}, extraSessionData) {
|
|
59
80
|
const executionId = this.logger.createExecutionContext(actionId);
|
|
60
81
|
const startTime = Date.now();
|
|
61
82
|
try {
|
|
@@ -73,6 +94,9 @@ class MockRunner {
|
|
|
73
94
|
// Deep clone to avoid mutations
|
|
74
95
|
const defaultPayload = JSON.parse(JSON.stringify(step.mock.defaultPayload));
|
|
75
96
|
const sessionData = await this.getSessionDataUpToStep(index);
|
|
97
|
+
if (extraSessionData) {
|
|
98
|
+
Object.assign(sessionData, extraSessionData);
|
|
99
|
+
}
|
|
76
100
|
// Validate inputs against schema if provided
|
|
77
101
|
if (step.mock.inputs?.jsonSchema && Object.keys(inputs).length > 0) {
|
|
78
102
|
// TODO: Add JSON schema validation for inputs
|
|
@@ -191,7 +215,7 @@ class MockRunner {
|
|
|
191
215
|
};
|
|
192
216
|
}
|
|
193
217
|
}
|
|
194
|
-
async runValidatePayload(actionId, targetPayload) {
|
|
218
|
+
async runValidatePayload(actionId, targetPayload, extraSessionData) {
|
|
195
219
|
try {
|
|
196
220
|
const baseActionId = MockRunner.resolveBaseActionId(actionId);
|
|
197
221
|
const step = this.config.steps.find((s) => s.action_id === baseActionId);
|
|
@@ -201,6 +225,9 @@ class MockRunner {
|
|
|
201
225
|
const index = this.config.steps.findIndex((s) => s.action_id === baseActionId);
|
|
202
226
|
const schema = (0, function_registry_1.getFunctionSchema)("validate");
|
|
203
227
|
const sessionData = await this.getSessionDataUpToStep(index);
|
|
228
|
+
if (extraSessionData) {
|
|
229
|
+
Object.assign(sessionData, extraSessionData);
|
|
230
|
+
}
|
|
204
231
|
const result = await this.getRunnerInstance().execute(MockRunner.decodeBase64(step.mock.validate), schema, [targetPayload, sessionData]);
|
|
205
232
|
return result;
|
|
206
233
|
}
|
package/dist/lib/configHelper.js
CHANGED
|
@@ -11,6 +11,7 @@ const MockRunner_1 = require("./MockRunner");
|
|
|
11
11
|
const uuid_1 = require("uuid");
|
|
12
12
|
const terser_1 = require("terser");
|
|
13
13
|
const validateConfig_1 = require("./utils/validateConfig");
|
|
14
|
+
const helpers_1 = require("./helpers");
|
|
14
15
|
function createInitialMockConfig(domain, version, flowId) {
|
|
15
16
|
return {
|
|
16
17
|
meta: {
|
|
@@ -32,65 +33,9 @@ function createInitialMockConfig(domain, version, flowId) {
|
|
|
32
33
|
steps: [],
|
|
33
34
|
transaction_history: [],
|
|
34
35
|
validationLib: "",
|
|
35
|
-
helperLib: MockRunner_1.MockRunner.encodeBase64(
|
|
36
|
+
helperLib: MockRunner_1.MockRunner.encodeBase64(helpers_1.DEFAULT_HELPER_LIB),
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
|
-
const defaultHelpers = `/*
|
|
39
|
-
Custom helper functions available in all mock generation functions.
|
|
40
|
-
these are appended below the generate function for each step.
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
const createFormURL = (domain,formId, sessionData) => {
|
|
44
|
-
const baseURL = sessionData.mockBaseUrl;
|
|
45
|
-
const transactionId = sessionData.transactionId[0];
|
|
46
|
-
const sessionId = sessionData.sessionId;
|
|
47
|
-
return \`\${baseURL}/forms/\${domain}/\${formId}/?transaction_id=\${transactionId}&session_id=\${sessionId}\`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Generates a UUID v4
|
|
51
|
-
function uuidv4() {
|
|
52
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
53
|
-
const r = Math.random() * 16 | 0;
|
|
54
|
-
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
55
|
-
return v.toString(16);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Generate a 6 digit string ID
|
|
60
|
-
function generate6DigitId() {
|
|
61
|
-
return Math.floor(100000 + Math.random() * 900000).toString();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Returns the current ISO timestamp
|
|
65
|
-
function currentTimestamp() {
|
|
66
|
-
return new Date().toISOString();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Converts ISO 8601 duration string to total seconds
|
|
70
|
-
const isoDurToSec = (duration) => {
|
|
71
|
-
const durRE = /P((\d+)Y)?((\d+)M)?((\d+)W)?((\d+)D)?T?((\d+)H)?((\d+)M)?((\d+)S)?/;
|
|
72
|
-
const s = durRE.exec(duration);
|
|
73
|
-
if (!s) return 0;
|
|
74
|
-
|
|
75
|
-
return (Number(s?.[2]) || 0) * 31536000 +
|
|
76
|
-
(Number(s?.[4]) || 0) * 2628288 +
|
|
77
|
-
(Number(s?.[6]) || 0) * 604800 +
|
|
78
|
-
(Number(s?.[8]) || 0) * 86400 +
|
|
79
|
-
(Number(s?.[10]) || 0) * 3600 +
|
|
80
|
-
(Number(s?.[12]) || 0) * 60 +
|
|
81
|
-
(Number(s?.[14]) || 0);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const setCityFromInputs = (payload, inputs) => {
|
|
85
|
-
if (!inputs) return "*";
|
|
86
|
-
let version = payload.context.version || payload.context.core_version || "2.0.0";
|
|
87
|
-
if (version.startsWith("1")) {
|
|
88
|
-
payload.context.city = inputs.city_code ?? "*";
|
|
89
|
-
} else {
|
|
90
|
-
payload.context.location.city.code = inputs.city_code ?? "*";
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
`;
|
|
94
39
|
function convertToFlowConfig(config) {
|
|
95
40
|
const flowConfig = {};
|
|
96
41
|
flowConfig.id = config.meta.flowId;
|
|
@@ -172,13 +117,25 @@ function convertToFlowConfig(config) {
|
|
|
172
117
|
if (step.mock.inputs !== undefined &&
|
|
173
118
|
step.mock.inputs !== null &&
|
|
174
119
|
Object.keys(step.mock.inputs).length > 0) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
120
|
+
if (step.mock.inputs.id == "finvu_verification") {
|
|
121
|
+
flowStep.input = [
|
|
122
|
+
{
|
|
123
|
+
name: "finvu_verification",
|
|
124
|
+
label: "Complete Account Aggregator Verification",
|
|
125
|
+
type: "FINVU_REDIRECT",
|
|
126
|
+
payloadField: "$.context.aa_consent_verified",
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
flowStep.input = [
|
|
132
|
+
{
|
|
133
|
+
name: step.mock.inputs.id,
|
|
134
|
+
type: step.mock.inputs.id,
|
|
135
|
+
schema: step.mock.inputs.jsonSchema,
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
}
|
|
182
139
|
}
|
|
183
140
|
if (step.mock.inputs?.oldInputs) {
|
|
184
141
|
flowStep.input = step.mock.inputs.oldInputs;
|
|
@@ -26,7 +26,7 @@ exports.FUNCTION_REGISTRY = {
|
|
|
26
26
|
description: "The generated payload object to be sent in the API request",
|
|
27
27
|
},
|
|
28
28
|
description: "Generates the mock payload for an API call",
|
|
29
|
-
timeout:
|
|
29
|
+
timeout: 45 * 1000,
|
|
30
30
|
defaultBody: ` return defaultPayload;`,
|
|
31
31
|
template: (body) => `/**
|
|
32
32
|
* Generates the mock payload for an API call in the transaction flow.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const DEFAULT_HELPERS_RAW = "\n/*\n * Default helpers available to every `generate()` in a mock step.\n *\n * Authoring rules:\n * 1. Prefer `function` declarations \u2014 they hoist, so cross-helper calls\n * work regardless of order.\n * 2. No `require` / `import` inside function bodies. The VM sandbox has no\n * module system. Only sandbox-whitelisted globals (Math, Date, JSON, \u2026)\n * and sibling helpers are in scope.\n * 3. If the helper needs request-scope data, take `sessionData` as an\n * explicit parameter. Free-variable references to `sessionData` do NOT\n * resolve at runtime \u2014 helpers run at script scope, `sessionData` is\n * only a parameter of `generate()`.\n * 4. Document every helper with a leading JSDoc block (`@param`, `@returns`).\n *\n * The full file (minus the trailing `module.exports = {...}` block) is\n * embedded verbatim into the sandbox bundle by\n * `scripts/generate-helpers-source.js`, so JSDoc reaches end users unchanged.\n * Re-run `npm run helpers:gen` after editing.\n */\n\n/**\n * Resolve the BPP or BAP subscriber URL from session data.\n *\n * @param {Object} sessionData session data (reads `bppUri` or `bapUri`)\n * @param {\"bpp\"|\"bap\"|string} type subscriber kind; anything other than \"bpp\" returns bapUri\n * @returns {string} the subscriber URL\n */\nfunction getSubscriberUrl(sessionData, type) {\n\tif (type === \"bpp\") {\n\t\treturn sessionData.bppUri;\n\t} else {\n\t\treturn sessionData.bapUri;\n\t}\n}\n\n/**\n * Generate a UUID v4 (RFC 4122, random-based).\n *\n * @returns {string} a new UUID v4, e.g. \"550e8400-e29b-41d4-a716-446655440000\"\n */\nfunction uuidv4() {\n\treturn \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n\t\tconst r = (Math.random() * 16) | 0;\n\t\tconst v = c === \"x\" ? r : (r & 0x3) | 0x8;\n\t\treturn v.toString(16);\n\t});\n}\n\n/**\n * Generate a 6-digit numeric string ID in [100000, 999999].\n *\n * @returns {string} a zero-padded 6-digit numeric string\n */\nfunction generate6DigitId() {\n\treturn Math.floor(100000 + Math.random() * 900000).toString();\n}\n\n/**\n * Get the current ISO-8601 UTC timestamp.\n *\n * @returns {string} e.g. \"2026-04-23T12:34:56.789Z\"\n */\nfunction currentTimestamp() {\n\treturn new Date().toISOString();\n}\n\n/**\n * Convert an ISO 8601 duration string (e.g. \"PT1H30M\", \"P2DT3H\") to total seconds.\n *\n * Approximations used: 1 week = 7 days, 1 month \u2248 30.42 days (2628288 sec),\n * 1 year = 365 days. Not calendar-exact.\n *\n * @param {string} duration ISO 8601 duration string\n * @returns {number} total seconds; 0 when the input is unparseable\n */\nfunction isoDurToSec(duration) {\n\tconst durRE =\n\t\t/P((\\d+)Y)?((\\d+)M)?((\\d+)W)?((\\d+)D)?T?((\\d+)H)?((\\d+)M)?((\\d+)S)?/;\n\tconst s = durRE.exec(duration);\n\tif (!s) return 0;\n\n\treturn (\n\t\t(Number(s?.[2]) || 0) * 31536000 +\n\t\t(Number(s?.[4]) || 0) * 2628288 +\n\t\t(Number(s?.[6]) || 0) * 604800 +\n\t\t(Number(s?.[8]) || 0) * 86400 +\n\t\t(Number(s?.[10]) || 0) * 3600 +\n\t\t(Number(s?.[12]) || 0) * 60 +\n\t\t(Number(s?.[14]) || 0)\n\t);\n}\n\n/**\n * Mutate `payload.context` in place to set the city code from `inputs.city_code`.\n *\n * Version-aware: ONDC v1.x uses flat `context.city`, v2.x uses nested\n * `context.location.city.code`. Falls back to \"*\" when `city_code` is missing.\n * No-op when `inputs` is falsy.\n *\n * @param {Object} payload payload with a `context` to mutate\n * @param {Object|null|undefined} inputs object with optional `city_code`\n * @returns {string|undefined} \"*\" when inputs is falsy; otherwise undefined\n */\nfunction setCityFromInputs(payload, inputs) {\n\tif (!inputs) return \"*\";\n\tconst version =\n\t\tpayload.context.version || payload.context.core_version || \"2.0.0\";\n\tif (version.startsWith(\"1\")) {\n\t\tpayload.context.city = inputs.city_code ?? \"*\";\n\t} else {\n\t\tpayload.context.location.city.code = inputs.city_code ?? \"*\";\n\t}\n}\n\n/**\n * Build a form submission URL from session data.\n *\n * @param {string} domain ONDC domain (e.g. \"ONDC:RET10\")\n * @param {string} formId form identifier\n * @param {Object} sessionData reads `mockBaseUrl`, `transactionId[0]`, `sessionId`\n * @returns {string} `${baseURL}/forms/${domain}/${formId}/?transaction_id=...&session_id=...`\n */\nfunction createFormURL(domain, formId, sessionData) {\n\tconst baseURL = sessionData.mockBaseUrl;\n\tconst transactionId = sessionData.transactionId[0];\n\tconst sessionId = sessionData.sessionId;\n\treturn `${baseURL}/forms/${domain}/${formId}/?transaction_id=${transactionId}&session_id=${sessionId}`;\n}\n\n/**\n * Generate a consent handler from the Finvu AA Service.\n *\n * Reads the service base URL from `sessionData.finvuUrl` \u2014 the installing\n * service MUST include that origin in\n * MockRunner.initSharedRunner({ allowedFetchBaseUrls: [...] })\n * otherwise the sandboxed fetch will be blocked.\n *\n * Times out after 10s via AbortController.\n *\n * @param {Object} sessionData session data; `sessionData.finvuUrl` is required\n * @param {Object} params\n * @param {string} params.custId customer ID (required)\n * @param {string} [params.templateName] defaults to \"FINVUDEMO_TESTING\"\n * @param {string} [params.consentDescription] defaults to \"Gold Loan Account Aggregator Consent\"\n * @param {string} [params.redirectUrl] defaults to \"https://google.co.in\"\n * @returns {Promise<string>} the `consentHandler` returned by the AA service\n * @throws {Error} when `custId` or `sessionData.finvuUrl` is missing, on non-OK\n * response, on missing `consentHandler` in the body, or on 10s timeout\n */\nasync function generateConsentHandler(\n\tsessionData,\n\t{\n\t\tcustId,\n\t\ttemplateName = \"FINVUDEMO_TESTING\",\n\t\tconsentDescription = \"Gold Loan Account Aggregator Consent\",\n\t\tredirectUrl = \"https://google.co.in\",\n\t},\n) {\n\tif (!custId) {\n\t\tthrow new Error(\"custId is required\");\n\t}\n\tconst baseUrl = sessionData && sessionData.finvuUrl;\n\tif (!baseUrl) {\n\t\tthrow new Error(\"sessionData.finvuUrl is required\");\n\t}\n\n\tconst url = `${baseUrl}/finvu-aa/consent/generate`;\n\n\tconst payload = {\n\t\tcustId,\n\t\ttemplateName,\n\t\tconsentDescription,\n\t\tredirectUrl,\n\t};\n\n\tconsole.log(\"Calling Finvu AA Service:\", url);\n\tconsole.log(\"Consent request payload:\", payload);\n\n\tconst controller = new AbortController();\n\tconst timeout = setTimeout(() => controller.abort(), 10000);\n\n\ttry {\n\t\tconst res = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify(payload),\n\t\t\tsignal: controller.signal,\n\t\t});\n\n\t\tif (!res.ok) {\n\t\t\tconst text = await res.text();\n\t\t\tthrow new Error(`Request failed: ${res.status} ${text}`);\n\t\t}\n\n\t\tconst data = await res.json();\n\n\t\tif (!data || !data.consentHandler) {\n\t\t\tthrow new Error(\"Invalid response: consentHandler missing\");\n\t\t}\n\n\t\treturn data.consentHandler;\n\t} catch (err) {\n\t\tif (err && err.name === \"AbortError\") {\n\t\t\tthrow new Error(\"Request timed out after 10 seconds\");\n\t\t}\n\t\tthrow err;\n\t} finally {\n\t\tclearTimeout(timeout);\n\t}\n}\n";
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_HELPERS_RAW = void 0;
|
|
4
|
+
// AUTO-GENERATED by scripts/generate-helpers-source.js. Do not edit.
|
|
5
|
+
// Source: src/lib/helpers/default-helpers.js
|
|
6
|
+
/* eslint-disable */
|
|
7
|
+
exports.DEFAULT_HELPERS_RAW = `
|
|
8
|
+
/*
|
|
9
|
+
* Default helpers available to every \`generate()\` in a mock step.
|
|
10
|
+
*
|
|
11
|
+
* Authoring rules:
|
|
12
|
+
* 1. Prefer \`function\` declarations — they hoist, so cross-helper calls
|
|
13
|
+
* work regardless of order.
|
|
14
|
+
* 2. No \`require\` / \`import\` inside function bodies. The VM sandbox has no
|
|
15
|
+
* module system. Only sandbox-whitelisted globals (Math, Date, JSON, …)
|
|
16
|
+
* and sibling helpers are in scope.
|
|
17
|
+
* 3. If the helper needs request-scope data, take \`sessionData\` as an
|
|
18
|
+
* explicit parameter. Free-variable references to \`sessionData\` do NOT
|
|
19
|
+
* resolve at runtime — helpers run at script scope, \`sessionData\` is
|
|
20
|
+
* only a parameter of \`generate()\`.
|
|
21
|
+
* 4. Document every helper with a leading JSDoc block (\`@param\`, \`@returns\`).
|
|
22
|
+
*
|
|
23
|
+
* The full file (minus the trailing \`module.exports = {...}\` block) is
|
|
24
|
+
* embedded verbatim into the sandbox bundle by
|
|
25
|
+
* \`scripts/generate-helpers-source.js\`, so JSDoc reaches end users unchanged.
|
|
26
|
+
* Re-run \`npm run helpers:gen\` after editing.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the BPP or BAP subscriber URL from session data.
|
|
31
|
+
*
|
|
32
|
+
* @param {Object} sessionData session data (reads \`bppUri\` or \`bapUri\`)
|
|
33
|
+
* @param {"bpp"|"bap"|string} type subscriber kind; anything other than "bpp" returns bapUri
|
|
34
|
+
* @returns {string} the subscriber URL
|
|
35
|
+
*/
|
|
36
|
+
function getSubscriberUrl(sessionData, type) {
|
|
37
|
+
if (type === "bpp") {
|
|
38
|
+
return sessionData.bppUri;
|
|
39
|
+
} else {
|
|
40
|
+
return sessionData.bapUri;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generate a UUID v4 (RFC 4122, random-based).
|
|
46
|
+
*
|
|
47
|
+
* @returns {string} a new UUID v4, e.g. "550e8400-e29b-41d4-a716-446655440000"
|
|
48
|
+
*/
|
|
49
|
+
function uuidv4() {
|
|
50
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
|
51
|
+
const r = (Math.random() * 16) | 0;
|
|
52
|
+
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
53
|
+
return v.toString(16);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate a 6-digit numeric string ID in [100000, 999999].
|
|
59
|
+
*
|
|
60
|
+
* @returns {string} a zero-padded 6-digit numeric string
|
|
61
|
+
*/
|
|
62
|
+
function generate6DigitId() {
|
|
63
|
+
return Math.floor(100000 + Math.random() * 900000).toString();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get the current ISO-8601 UTC timestamp.
|
|
68
|
+
*
|
|
69
|
+
* @returns {string} e.g. "2026-04-23T12:34:56.789Z"
|
|
70
|
+
*/
|
|
71
|
+
function currentTimestamp() {
|
|
72
|
+
return new Date().toISOString();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Convert an ISO 8601 duration string (e.g. "PT1H30M", "P2DT3H") to total seconds.
|
|
77
|
+
*
|
|
78
|
+
* Approximations used: 1 week = 7 days, 1 month ≈ 30.42 days (2628288 sec),
|
|
79
|
+
* 1 year = 365 days. Not calendar-exact.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} duration ISO 8601 duration string
|
|
82
|
+
* @returns {number} total seconds; 0 when the input is unparseable
|
|
83
|
+
*/
|
|
84
|
+
function isoDurToSec(duration) {
|
|
85
|
+
const durRE =
|
|
86
|
+
/P((\\d+)Y)?((\\d+)M)?((\\d+)W)?((\\d+)D)?T?((\\d+)H)?((\\d+)M)?((\\d+)S)?/;
|
|
87
|
+
const s = durRE.exec(duration);
|
|
88
|
+
if (!s) return 0;
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
(Number(s?.[2]) || 0) * 31536000 +
|
|
92
|
+
(Number(s?.[4]) || 0) * 2628288 +
|
|
93
|
+
(Number(s?.[6]) || 0) * 604800 +
|
|
94
|
+
(Number(s?.[8]) || 0) * 86400 +
|
|
95
|
+
(Number(s?.[10]) || 0) * 3600 +
|
|
96
|
+
(Number(s?.[12]) || 0) * 60 +
|
|
97
|
+
(Number(s?.[14]) || 0)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Mutate \`payload.context\` in place to set the city code from \`inputs.city_code\`.
|
|
103
|
+
*
|
|
104
|
+
* Version-aware: ONDC v1.x uses flat \`context.city\`, v2.x uses nested
|
|
105
|
+
* \`context.location.city.code\`. Falls back to "*" when \`city_code\` is missing.
|
|
106
|
+
* No-op when \`inputs\` is falsy.
|
|
107
|
+
*
|
|
108
|
+
* @param {Object} payload payload with a \`context\` to mutate
|
|
109
|
+
* @param {Object|null|undefined} inputs object with optional \`city_code\`
|
|
110
|
+
* @returns {string|undefined} "*" when inputs is falsy; otherwise undefined
|
|
111
|
+
*/
|
|
112
|
+
function setCityFromInputs(payload, inputs) {
|
|
113
|
+
if (!inputs) return "*";
|
|
114
|
+
const version =
|
|
115
|
+
payload.context.version || payload.context.core_version || "2.0.0";
|
|
116
|
+
if (version.startsWith("1")) {
|
|
117
|
+
payload.context.city = inputs.city_code ?? "*";
|
|
118
|
+
} else {
|
|
119
|
+
payload.context.location.city.code = inputs.city_code ?? "*";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build a form submission URL from session data.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} domain ONDC domain (e.g. "ONDC:RET10")
|
|
127
|
+
* @param {string} formId form identifier
|
|
128
|
+
* @param {Object} sessionData reads \`mockBaseUrl\`, \`transactionId[0]\`, \`sessionId\`
|
|
129
|
+
* @returns {string} \`\${baseURL}/forms/\${domain}/\${formId}/?transaction_id=...&session_id=...\`
|
|
130
|
+
*/
|
|
131
|
+
function createFormURL(domain, formId, sessionData) {
|
|
132
|
+
const baseURL = sessionData.mockBaseUrl;
|
|
133
|
+
const transactionId = sessionData.transactionId[0];
|
|
134
|
+
const sessionId = sessionData.sessionId;
|
|
135
|
+
return \`\${baseURL}/forms/\${domain}/\${formId}/?transaction_id=\${transactionId}&session_id=\${sessionId}\`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Generate a consent handler from the Finvu AA Service.
|
|
140
|
+
*
|
|
141
|
+
* Reads the service base URL from \`sessionData.finvuUrl\` — the installing
|
|
142
|
+
* service MUST include that origin in
|
|
143
|
+
* MockRunner.initSharedRunner({ allowedFetchBaseUrls: [...] })
|
|
144
|
+
* otherwise the sandboxed fetch will be blocked.
|
|
145
|
+
*
|
|
146
|
+
* Times out after 10s via AbortController.
|
|
147
|
+
*
|
|
148
|
+
* @param {Object} sessionData session data; \`sessionData.finvuUrl\` is required
|
|
149
|
+
* @param {Object} params
|
|
150
|
+
* @param {string} params.custId customer ID (required)
|
|
151
|
+
* @param {string} [params.templateName] defaults to "FINVUDEMO_TESTING"
|
|
152
|
+
* @param {string} [params.consentDescription] defaults to "Gold Loan Account Aggregator Consent"
|
|
153
|
+
* @param {string} [params.redirectUrl] defaults to "https://google.co.in"
|
|
154
|
+
* @returns {Promise<string>} the \`consentHandler\` returned by the AA service
|
|
155
|
+
* @throws {Error} when \`custId\` or \`sessionData.finvuUrl\` is missing, on non-OK
|
|
156
|
+
* response, on missing \`consentHandler\` in the body, or on 10s timeout
|
|
157
|
+
*/
|
|
158
|
+
async function generateConsentHandler(
|
|
159
|
+
sessionData,
|
|
160
|
+
{
|
|
161
|
+
custId,
|
|
162
|
+
templateName = "FINVUDEMO_TESTING",
|
|
163
|
+
consentDescription = "Gold Loan Account Aggregator Consent",
|
|
164
|
+
redirectUrl = "https://google.co.in",
|
|
165
|
+
},
|
|
166
|
+
) {
|
|
167
|
+
if (!custId) {
|
|
168
|
+
throw new Error("custId is required");
|
|
169
|
+
}
|
|
170
|
+
const baseUrl = sessionData && sessionData.finvuUrl;
|
|
171
|
+
if (!baseUrl) {
|
|
172
|
+
throw new Error("sessionData.finvuUrl is required");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const url = \`\${baseUrl}/finvu-aa/consent/generate\`;
|
|
176
|
+
|
|
177
|
+
const payload = {
|
|
178
|
+
custId,
|
|
179
|
+
templateName,
|
|
180
|
+
consentDescription,
|
|
181
|
+
redirectUrl,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
console.log("Calling Finvu AA Service:", url);
|
|
185
|
+
console.log("Consent request payload:", payload);
|
|
186
|
+
|
|
187
|
+
const controller = new AbortController();
|
|
188
|
+
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const res = await fetch(url, {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: {
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
},
|
|
196
|
+
body: JSON.stringify(payload),
|
|
197
|
+
signal: controller.signal,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (!res.ok) {
|
|
201
|
+
const text = await res.text();
|
|
202
|
+
throw new Error(\`Request failed: \${res.status} \${text}\`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const data = await res.json();
|
|
206
|
+
|
|
207
|
+
if (!data || !data.consentHandler) {
|
|
208
|
+
throw new Error("Invalid response: consentHandler missing");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return data.consentHandler;
|
|
212
|
+
} catch (err) {
|
|
213
|
+
if (err && err.name === "AbortError") {
|
|
214
|
+
throw new Error("Request timed out after 10 seconds");
|
|
215
|
+
}
|
|
216
|
+
throw err;
|
|
217
|
+
} finally {
|
|
218
|
+
clearTimeout(timeout);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
`;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the BPP or BAP subscriber URL from session data.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} sessionData session data (reads `bppUri` or `bapUri`)
|
|
5
|
+
* @param {"bpp"|"bap"|string} type subscriber kind; anything other than "bpp" returns bapUri
|
|
6
|
+
* @returns {string} the subscriber URL
|
|
7
|
+
*/
|
|
8
|
+
export function getSubscriberUrl(sessionData: Object, type: "bpp" | "bap" | string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Generate a UUID v4 (RFC 4122, random-based).
|
|
11
|
+
*
|
|
12
|
+
* @returns {string} a new UUID v4, e.g. "550e8400-e29b-41d4-a716-446655440000"
|
|
13
|
+
*/
|
|
14
|
+
export function uuidv4(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generate a 6-digit numeric string ID in [100000, 999999].
|
|
17
|
+
*
|
|
18
|
+
* @returns {string} a zero-padded 6-digit numeric string
|
|
19
|
+
*/
|
|
20
|
+
export function generate6DigitId(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Get the current ISO-8601 UTC timestamp.
|
|
23
|
+
*
|
|
24
|
+
* @returns {string} e.g. "2026-04-23T12:34:56.789Z"
|
|
25
|
+
*/
|
|
26
|
+
export function currentTimestamp(): string;
|
|
27
|
+
/**
|
|
28
|
+
* Convert an ISO 8601 duration string (e.g. "PT1H30M", "P2DT3H") to total seconds.
|
|
29
|
+
*
|
|
30
|
+
* Approximations used: 1 week = 7 days, 1 month ≈ 30.42 days (2628288 sec),
|
|
31
|
+
* 1 year = 365 days. Not calendar-exact.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} duration ISO 8601 duration string
|
|
34
|
+
* @returns {number} total seconds; 0 when the input is unparseable
|
|
35
|
+
*/
|
|
36
|
+
export function isoDurToSec(duration: string): number;
|
|
37
|
+
/**
|
|
38
|
+
* Mutate `payload.context` in place to set the city code from `inputs.city_code`.
|
|
39
|
+
*
|
|
40
|
+
* Version-aware: ONDC v1.x uses flat `context.city`, v2.x uses nested
|
|
41
|
+
* `context.location.city.code`. Falls back to "*" when `city_code` is missing.
|
|
42
|
+
* No-op when `inputs` is falsy.
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} payload payload with a `context` to mutate
|
|
45
|
+
* @param {Object|null|undefined} inputs object with optional `city_code`
|
|
46
|
+
* @returns {string|undefined} "*" when inputs is falsy; otherwise undefined
|
|
47
|
+
*/
|
|
48
|
+
export function setCityFromInputs(payload: Object, inputs: Object | null | undefined): string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Build a form submission URL from session data.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} domain ONDC domain (e.g. "ONDC:RET10")
|
|
53
|
+
* @param {string} formId form identifier
|
|
54
|
+
* @param {Object} sessionData reads `mockBaseUrl`, `transactionId[0]`, `sessionId`
|
|
55
|
+
* @returns {string} `${baseURL}/forms/${domain}/${formId}/?transaction_id=...&session_id=...`
|
|
56
|
+
*/
|
|
57
|
+
export function createFormURL(domain: string, formId: string, sessionData: Object): string;
|
|
58
|
+
/**
|
|
59
|
+
* Generate a consent handler from the Finvu AA Service.
|
|
60
|
+
*
|
|
61
|
+
* Reads the service base URL from `sessionData.finvuUrl` — the installing
|
|
62
|
+
* service MUST include that origin in
|
|
63
|
+
* MockRunner.initSharedRunner({ allowedFetchBaseUrls: [...] })
|
|
64
|
+
* otherwise the sandboxed fetch will be blocked.
|
|
65
|
+
*
|
|
66
|
+
* Times out after 10s via AbortController.
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} sessionData session data; `sessionData.finvuUrl` is required
|
|
69
|
+
* @param {Object} params
|
|
70
|
+
* @param {string} params.custId customer ID (required)
|
|
71
|
+
* @param {string} [params.templateName] defaults to "FINVUDEMO_TESTING"
|
|
72
|
+
* @param {string} [params.consentDescription] defaults to "Gold Loan Account Aggregator Consent"
|
|
73
|
+
* @param {string} [params.redirectUrl] defaults to "https://google.co.in"
|
|
74
|
+
* @returns {Promise<string>} the `consentHandler` returned by the AA service
|
|
75
|
+
* @throws {Error} when `custId` or `sessionData.finvuUrl` is missing, on non-OK
|
|
76
|
+
* response, on missing `consentHandler` in the body, or on 10s timeout
|
|
77
|
+
*/
|
|
78
|
+
export function generateConsentHandler(sessionData: Object, { custId, templateName, consentDescription, redirectUrl, }: {
|
|
79
|
+
custId: string;
|
|
80
|
+
templateName?: string | undefined;
|
|
81
|
+
consentDescription?: string | undefined;
|
|
82
|
+
redirectUrl?: string | undefined;
|
|
83
|
+
}): Promise<string>;
|