Skip to content

Conversation

jcortes
Copy link
Collaborator

@jcortes jcortes commented Oct 14, 2025

WHY

Resolves #9188

Summary by CodeRabbit

  • New Features

    • Added an InMobile “Send SMS Messages” action supporting batch sends and a completion summary.
    • Added flexible parsing utilities to accept messages as arrays or JSON-like strings.
  • Refactor

    • Centralized InMobile request and authentication handling for more reliable HTTP calls.
  • Chores

    • Bumped InMobile component version to 0.1.0.

@jcortes jcortes self-assigned this Oct 14, 2025
Copy link

vercel bot commented Oct 14, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
pipedream-docs Ignored Ignored Oct 15, 2025 3:16pm
pipedream-docs-redirect-do-not-edit Ignored Ignored Oct 15, 2025 3:16pm

Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
New Action: Send SMS Messages
components/inmobile/actions/send-sms-messages/send-sms-messages.mjs
Adds default exported action with metadata and public props (app, messages); run() parses messages via utils.parseArray, calls app.sendSms, returns API response and sets a summary with the count sent.
Utilities for Parsing
components/inmobile/common/utils.mjs
Introduces parseJson(input, maxDepth = 100) and parseArray(input, maxDepth = 100) for safe, depth-limited recursive parsing of JSON-like strings (with cycle protection). Exports both in the module's default export.
App Refactor and HTTP Helpers
components/inmobile/inmobile.app.mjs
Removes propDefinitions and authKeys. Adds getUrl, getAuth (basic auth using api_key), makeRequest (axios-based wrapper), post, and sendSms (POST to /sms/outgoing) to centralize HTTP communication.
Package Metadata
components/inmobile/package.json
Bumps version 0.0.10.1.0 and adds dependency @pipedream/platform: ^3.1.0.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I twitch my ears and parse each line,
Arrays hop through JSON vine by vine.
I route the bytes to an API den,
Count the hops and send again.
Version bumped, carrots tucked away—SMS bright as day. 🥕📨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description only includes the template heading “## WHY” followed by “Resolves #9188” and lacks any explanation of the motivation or context as required by the repository’s description template. Please expand the “## WHY” section to describe the rationale and context for integrating the InMobile component and adding the send SMS messages action, following the repository’s description template.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title “[Component] InMobile - Send SMS messages” succinctly and accurately describes the main change by naming the InMobile component and the new action for sending SMS messages, which aligns directly with the changes introduced.
Linked Issues Check ✅ Passed The changes fully implement the objectives from issue #9188 by integrating the InMobile REST API through new app methods and providing the Send SMS Messages action, satisfying both the integration and action requirements.
Out of Scope Changes Check ✅ Passed All modifications, including the new utility functions, app HTTP helpers, action implementation, and dependency updates, are directly related to the InMobile integration and the Send SMS Messages action, with no unrelated changes present.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch inmobile-send-sms-messges-action

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 parseArray

Ensure 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

📥 Commits

Reviewing files that changed from the base of the PR and between 25fe600 and 2e5902a.

⛔ 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 good

No issues spotted. Please confirm the runtime uses @pipedream/platform >= 3.1.x to avoid runtime mismatches.

Also applies to: 15-17

@jcortes jcortes force-pushed the inmobile-send-sms-messges-action branch from 2e5902a to b512325 Compare October 15, 2025 15:16
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 as string[] (line 18), but the entire description (lines 20-54) documents message objects with fields like to, 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:

  1. Preferred: Change the prop type to accept objects directly so users can pass [{ to: "...", text: "...", from: "..." }] without manual JSON stringification.
  2. 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:

  1. 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.

  2. No message count validation: The InMobile API requires 1-250 messages. This is not enforced.

  3. No required field validation: The code doesn't check that each message has to, text, and from fields, leading to cryptic API errors.

  4. Incorrect summary (line 70): Uses original messages.length instead of the parsed/flattened count, so the summary may report the wrong number.

  5. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2e5902a and b512325.

⛔ 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.

Comment on lines +1 to +40
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);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Incomplete depth limiting and cycle detection for arrays.

The parseJson function has several correctness issues:

  1. 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.

  2. 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.

  3. Inconsistent recursion (line 15): Recursive call uses parseJson(JSON.parse(value), maxDepth - 1) instead of the inner parse function, which bypasses the seen 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.

Suggested change
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.

Comment on lines +42 to +63
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Depth limit not enforced; inconsistent error handling.

The parseArray function has correctness issues:

  1. No depth decrement (lines 49, 59): Recursive calls to parseArray don't decrement maxDepth, allowing unbounded recursion and stack overflow.

  2. 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 to parseJson (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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InMobile

1 participant