-
Notifications
You must be signed in to change notification settings - Fork 5.5k
[Component] InMobile - Send SMS messages #18760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
WalkthroughAdds an InMobile "Send SMS Messages" action, safe JSON/array parsing utilities, and refactors the InMobile app to centralize HTTP requests (including a sendSms method). Also bumps package version and adds an @pipedream/platform dependency. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant A as Action: send-sms-messages
participant UTL as Utils
participant APP as InMobile App
participant API as InMobile REST API
U->>A: invoke action with `app` + `messages`
A->>UTL: parseArray(messages)
UTL-->>A: parsed messages[]
A->>APP: sendSms({ messages: parsed messages })
APP->>APP: getAuth() / getUrl("/sms/outgoing")
APP->>API: POST /sms/outgoing (auth + payload)
API-->>APP: response
APP-->>A: response
A-->>U: returns response and summary ("N messages sent")
note right of APP: makeRequest/post centralize HTTP calls
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (1)
components/inmobile/common/utils.mjs (1)
58-60
: Flatten nested arrays and decrement depth in parseArrayEnsure consistent depth handling and normalize nested arrays (common when strings contain JSON arrays).
Apply this diff:
- if (Array.isArray(input)) { - return input.map((item) => parseArray(item, maxDepth)); - } + if (Array.isArray(input)) { + return input.flatMap((item) => { + const v = parseArray(item, maxDepth - 1); + return Array.isArray(v) ? v : [v]; + }); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
components/inmobile/actions/send-sms-messages/send-sms-messages.mjs
(1 hunks)components/inmobile/common/utils.mjs
(1 hunks)components/inmobile/inmobile.app.mjs
(1 hunks)components/inmobile/package.json
(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Lint Code Base
- GitHub Check: Publish TypeScript components
- GitHub Check: Verify TypeScript components
- GitHub Check: pnpm publish
🔇 Additional comments (1)
components/inmobile/package.json (1)
3-3
: Version bump and platform dependency look goodNo issues spotted. Please confirm the runtime uses @pipedream/platform >= 3.1.x to avoid runtime mismatches.
Also applies to: 15-17
2e5902a
to
b512325
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
components/inmobile/actions/send-sms-messages/send-sms-messages.mjs (2)
18-55
: Critical: Prop type contradicts documentation and example.The
messages
prop is declared asstring[]
(line 18), but the entire description (lines 20-54) documents message objects with fields liketo
,text
,from
, etc., and the example (lines 39-53) shows an array of objects—not strings.This type mismatch will confuse users and break at runtime when the action attempts to use string elements as objects.
Resolution options:
- Preferred: Change the prop type to accept objects directly so users can pass
[{ to: "...", text: "...", from: "..." }]
without manual JSON stringification.- Alternative: Keep
string[]
but clarify that each string must be a valid JSON object (not a top-level JSON array), and update the example to show individual stringified objects.Would you like me to generate the updated prop definition for option 1?
63-71
: Critical: Missing validation, incorrect count, and nested array risk.The current implementation has multiple critical issues:
No flattening (line 66):
utils.parseArray(messages)
can return nested arrays if any element is a JSON array string (e.g.,["[{...}, {...}]"]
becomes[[{...}, {...}]]
), breaking the API call.No message count validation: The InMobile API requires 1-250 messages. This is not enforced.
No required field validation: The code doesn't check that each message has
to
,text
, andfrom
fields, leading to cryptic API errors.Incorrect summary (line 70): Uses original
messages.length
instead of the parsed/flattened count, so the summary may report the wrong number.No idempotency safeguard: Without enforcing or auto-assigning
messageId
, retries will send duplicate messages.Apply this diff to address all issues:
+ // Normalize inputs: accept string[], object[], or JSON array strings within the array + const normalizedMessages = (Array.isArray(messages) ? messages : [messages]) + .flatMap((m) => { + const v = utils.parseArray(m); + return Array.isArray(v) ? v : [v]; + }); + + if (normalizedMessages.length < 1 || normalizedMessages.length > 250) { + throw new Error("Messages must contain between 1 and 250 items."); + } + normalizedMessages.forEach((msg, i) => { + if (!msg || typeof msg !== "object") { + throw new Error(`Message at index ${i} is not an object.`); + } + const { to, text, from } = msg; + if (!to || !text || !from) { + throw new Error(`Message at index ${i} is missing required field(s): to, text, from.`); + } + }); + const response = await app.sendSms({ $, data: { - messages: utils.parseArray(messages), + messages: normalizedMessages, }, }); - $.export("$summary", `Successfully sent ${messages.length} SMS message(s)`); + $.export("$summary", `Successfully sent ${normalizedMessages.length} SMS message(s)`); return response;Note on idempotency: Consider requiring or auto-assigning
messageId
for each message to prevent duplicate sends on retries.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
components/inmobile/actions/send-sms-messages/send-sms-messages.mjs
(1 hunks)components/inmobile/common/utils.mjs
(1 hunks)components/inmobile/inmobile.app.mjs
(1 hunks)components/inmobile/package.json
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- components/inmobile/package.json
- components/inmobile/inmobile.app.mjs
🧰 Additional context used
🧬 Code graph analysis (1)
components/inmobile/common/utils.mjs (1)
components/google_cloud/sources/bigquery-query-results/bigquery-query-results.mjs (1)
key
(48-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: pnpm publish
- GitHub Check: Lint Code Base
- GitHub Check: Publish TypeScript components
- GitHub Check: Verify TypeScript components
🔇 Additional comments (1)
components/inmobile/common/utils.mjs (1)
65-68
: LGTM!The default export correctly exposes both utility functions.
const parseJson = (input, maxDepth = 100) => { | ||
const seen = new WeakSet(); | ||
const parse = (value) => { | ||
if (maxDepth <= 0) { | ||
return value; | ||
} | ||
if (typeof(value) === "string") { | ||
// Only parse if the string looks like a JSON object or array | ||
const trimmed = value.trim(); | ||
if ( | ||
(trimmed.startsWith("{") && trimmed.endsWith("}")) || | ||
(trimmed.startsWith("[") && trimmed.endsWith("]")) | ||
) { | ||
try { | ||
return parseJson(JSON.parse(value), maxDepth - 1); | ||
} catch (e) { | ||
return value; | ||
} | ||
} | ||
return value; | ||
} else if (typeof(value) === "object" && value !== null && !Array.isArray(value)) { | ||
if (seen.has(value)) { | ||
return value; | ||
} | ||
seen.add(value); | ||
return Object.entries(value) | ||
.reduce((acc, [ | ||
key, | ||
val, | ||
]) => Object.assign(acc, { | ||
[key]: parse(val), | ||
}), {}); | ||
} else if (Array.isArray(value)) { | ||
return value.map((item) => parse(item)); | ||
} | ||
return value; | ||
}; | ||
|
||
return parse(input); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Incomplete depth limiting and cycle detection for arrays.
The parseJson
function has several correctness issues:
-
Depth not decremented for object/array traversal (lines 26-34):
maxDepth
is only decremented when recursively parsing JSON strings (line 15), but when traversing existing objects or arrays (lines 26-34), the depth counter is not reduced. This allows deep nesting to bypass the depth limit. -
Arrays not checked for cycles (line 33-34): While objects are checked against the
seen
WeakSet (line 22), arrays are not. This creates an infinite loop if an array contains a circular reference to itself. -
Inconsistent recursion (line 15): Recursive call uses
parseJson(JSON.parse(value), maxDepth - 1)
instead of the innerparse
function, which bypasses theseen
set and starts fresh cycle detection. This breaks cycle protection across recursive calls.
Apply this diff to fix depth limiting and cycle detection:
const parseJson = (input, maxDepth = 100) => {
const seen = new WeakSet();
- const parse = (value) => {
+ const parse = (value, depth) => {
- if (maxDepth <= 0) {
+ if (depth <= 0) {
return value;
}
if (typeof(value) === "string") {
// Only parse if the string looks like a JSON object or array
const trimmed = value.trim();
if (
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
(trimmed.startsWith("[") && trimmed.endsWith("]"))
) {
try {
- return parseJson(JSON.parse(value), maxDepth - 1);
+ return parse(JSON.parse(value), depth - 1);
} catch (e) {
return value;
}
}
return value;
} else if (typeof(value) === "object" && value !== null && !Array.isArray(value)) {
if (seen.has(value)) {
return value;
}
seen.add(value);
return Object.entries(value)
.reduce((acc, [
key,
val,
]) => Object.assign(acc, {
- [key]: parse(val),
+ [key]: parse(val, depth - 1),
}), {});
} else if (Array.isArray(value)) {
+ if (seen.has(value)) {
+ return value;
+ }
+ seen.add(value);
- return value.map((item) => parse(item));
+ return value.map((item) => parse(item, depth - 1));
}
return value;
};
- return parse(input);
+ return parse(input, maxDepth);
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const parseJson = (input, maxDepth = 100) => { | |
const seen = new WeakSet(); | |
const parse = (value) => { | |
if (maxDepth <= 0) { | |
return value; | |
} | |
if (typeof(value) === "string") { | |
// Only parse if the string looks like a JSON object or array | |
const trimmed = value.trim(); | |
if ( | |
(trimmed.startsWith("{") && trimmed.endsWith("}")) || | |
(trimmed.startsWith("[") && trimmed.endsWith("]")) | |
) { | |
try { | |
return parseJson(JSON.parse(value), maxDepth - 1); | |
} catch (e) { | |
return value; | |
} | |
} | |
return value; | |
} else if (typeof(value) === "object" && value !== null && !Array.isArray(value)) { | |
if (seen.has(value)) { | |
return value; | |
} | |
seen.add(value); | |
return Object.entries(value) | |
.reduce((acc, [ | |
key, | |
val, | |
]) => Object.assign(acc, { | |
[key]: parse(val), | |
}), {}); | |
} else if (Array.isArray(value)) { | |
return value.map((item) => parse(item)); | |
} | |
return value; | |
}; | |
return parse(input); | |
}; | |
const parseJson = (input, maxDepth = 100) => { | |
const seen = new WeakSet(); | |
const parse = (value, depth) => { | |
if (depth <= 0) { | |
return value; | |
} | |
if (typeof(value) === "string") { | |
// Only parse if the string looks like a JSON object or array | |
const trimmed = value.trim(); | |
if ( | |
(trimmed.startsWith("{") && trimmed.endsWith("}")) || | |
(trimmed.startsWith("[") && trimmed.endsWith("]")) | |
) { | |
try { | |
return parse(JSON.parse(value), depth - 1); | |
} catch (e) { | |
return value; | |
} | |
} | |
return value; | |
} else if (typeof(value) === "object" && value !== null && !Array.isArray(value)) { | |
if (seen.has(value)) { | |
return value; | |
} | |
seen.add(value); | |
return Object.entries(value) | |
.reduce((acc, [ | |
key, | |
val, | |
]) => Object.assign(acc, { | |
[key]: parse(val, depth - 1), | |
}), {}); | |
} else if (Array.isArray(value)) { | |
if (seen.has(value)) { | |
return value; | |
} | |
seen.add(value); | |
return value.map((item) => parse(item, depth - 1)); | |
} | |
return value; | |
}; | |
return parse(input, maxDepth); | |
}; |
🤖 Prompt for AI Agents
In components/inmobile/common/utils.mjs around lines 1–40, the recursive parse
logic fails to decrement maxDepth for object/array traversal, doesn't detect
cycles for arrays, and calls parseJson (resetting the seen set) when parsing
JSON strings; change the inner parse to accept a currentDepth parameter (or
otherwise decrement maxDepth on recursive calls), use parse(parsedValue,
currentDepth - 1) instead of parseJson(...) when parsing strings, and when
handling arrays check seen.has(value) and seen.add(value) just like for objects
before mapping; ensure every recursive call into objects/arrays passes a
decremented depth and reuses the same seen WeakSet so depth limiting and cycle
detection work consistently.
function parseArray(input, maxDepth = 100) { | ||
if (typeof input === "string") { | ||
const trimmed = input.trim(); | ||
if (trimmed.startsWith("[") && trimmed.endsWith("]")) { | ||
try { | ||
const parsed = JSON.parse(trimmed); | ||
if (Array.isArray(parsed)) { | ||
return parsed.map((item) => parseArray(item, maxDepth - 1)); | ||
} | ||
} catch (e) { | ||
throw new Error(`Invalid JSON array format: ${e.message}`); | ||
} | ||
} | ||
return parseJson(input, maxDepth); | ||
} | ||
|
||
if (Array.isArray(input)) { | ||
return input.map((item) => parseArray(item, maxDepth)); | ||
} | ||
|
||
return input; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Depth limit not enforced; inconsistent error handling.
The parseArray
function has correctness issues:
-
No depth decrement (lines 49, 59): Recursive calls to
parseArray
don't decrementmaxDepth
, allowing unbounded recursion and stack overflow. -
Inconsistent error handling (lines 45-55): If a string looks like an array but
JSON.parse
fails (line 52), an error is thrown. However, if the string doesn't look like an array at all, it falls back toparseJson
(line 55) which silently returns the input on parse errors. This creates unpredictable behavior:"[invalid"
throws, but"invalid"
returns silently.
Apply this diff to fix depth limiting and error handling:
function parseArray(input, maxDepth = 100) {
+ if (maxDepth <= 0) {
+ return input;
+ }
+
if (typeof input === "string") {
const trimmed = input.trim();
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
try {
const parsed = JSON.parse(trimmed);
if (Array.isArray(parsed)) {
return parsed.map((item) => parseArray(item, maxDepth - 1));
}
} catch (e) {
throw new Error(`Invalid JSON array format: ${e.message}`);
}
}
return parseJson(input, maxDepth);
}
if (Array.isArray(input)) {
return input.map((item) => parseArray(item, maxDepth - 1));
}
return input;
}
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In components/inmobile/common/utils.mjs around lines 42 to 63, enforce the
maxDepth limit and make error handling consistent: at the top of parseArray
return/throw when maxDepth <= 0 (e.g., throw a "max recursion depth exceeded"
error), decrement maxDepth on every recursive call (pass maxDepth - 1 when
calling parseArray for items and when delegating to parseJson), and stop
throwing on failed JSON.parse of a string-looking-array; instead, on JSON.parse
catch, fallback to calling parseJson(input, maxDepth - 1) so behavior matches
non-bracketed strings.
WHY
Resolves #9188
Summary by CodeRabbit
New Features
Refactor
Chores