Skip to content
Closed

#26399 #26909

Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 41 additions & 28 deletions apps/portal/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ const DEV_MODE_DATA = {
...Fixtures.paidMemberOnTier(),
pageData: Fixtures.offer
};

function safeJSONParse(value, fallback = null) {
try {
return JSON.Parse(value);
} catch (e) {
/* eslint-disable no-console */
console.warn('[Portal] Invalid JSON in URL parameter:', e.message);
return fallback;
}
}
Comment on lines +29 to +37
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix recursive call in safeJSONParse (stack overflow on every invocation).

Line 31 calls safeJSONParse from inside safeJSONParse, so each parse recurses until a RangeError occurs and then falls back. This is a critical correctness/performance issue. It should call JSON.parse(value) directly.

🐛 Proposed fix
 function safeJSONParse(value, fallback = null) {
     try {
-        return safeJSONParse(value);
+        return JSON.parse(value);
     } catch (e) {
         /* eslint-disable no-console */
         console.warn('[Portal] Invalid JSON in URL parameter:', e.message);
         return fallback;
     }
 }
📝 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
function safeJSONParse(value, fallback = null) {
try {
return safeJSONParse(value);
} catch (e) {
/* eslint-disable no-console */
console.warn('[Portal] Invalid JSON in URL parameter:', e.message);
return fallback;
}
}
function safeJSONParse(value, fallback = null) {
try {
return JSON.parse(value);
} catch (e) {
/* eslint-disable no-console */
console.warn('[Portal] Invalid JSON in URL parameter:', e.message);
return fallback;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/portal/src/app.js` around lines 29 - 37, safeJSONParse currently calls
itself recursively causing a stack overflow; replace the recursive call with a
direct JSON.parse(value) invocation and keep the existing try/catch/fallback
behavior so function safeJSONParse(value, fallback = null) attempts
JSON.parse(value) and on any exception logs the warning via console.warn and
returns fallback.

function SentryErrorBoundary({site, children}) {
const {portal_sentry: portalSentry} = site || {};
if (portalSentry && portalSentry.dsn) {
Expand Down Expand Up @@ -383,14 +391,14 @@ export default class App extends React.Component {
const value = decodeURIComponent(pair[1]);

if (key === 'button') {
data.site.portal_button = JSON.parse(value);
data.site.portal_button = safeJSONParse(value);
} else if (key === 'name') {
data.site.portal_name = JSON.parse(value);
} else if (key === 'isFree' && JSON.parse(value)) {
data.site.portal_name = safeJSONParse(value);
} else if (key === 'isFree' && safeJSONParse(value)) {
allowedPlans.push('free');
} else if (key === 'isMonthly' && JSON.parse(value)) {
} else if (key === 'isMonthly' && safeJSONParse(value)) {
allowedPlans.push('monthly');
} else if (key === 'isYearly' && JSON.parse(value)) {
} else if (key === 'isYearly' && safeJSONParse(value)) {
allowedPlans.push('yearly');
} else if (key === 'portalPrices') {
portalPrices = value ? value.split(',') : [];
Expand All @@ -407,7 +415,7 @@ export default class App extends React.Component {
} else if (key === 'signupTermsHtml') {
data.site.portal_signup_terms_html = value || '';
} else if (key === 'signupCheckboxRequired') {
data.site.portal_signup_checkbox_required = JSON.parse(value);
data.site.portal_signup_checkbox_required = safeJSONParse(value);
} else if (key === 'buttonStyle' && value) {
data.site.portal_button_style = value;
} else if (key === 'monthlyPrice' && !isNaN(Number(value))) {
Expand All @@ -422,13 +430,13 @@ export default class App extends React.Component {
data.site.plans.currency_symbol = getCurrencySymbol(currencyValue);
currency = currencyValue;
} else if (key === 'disableBackground') {
data.site.disableBackground = JSON.parse(value);
data.site.disableBackground = safeJSONParse(value);
} else if (key === 'membersSignupAccess' && value) {
data.site.members_signup_access = value;
} else if (key === 'portalDefaultPlan' && value) {
data.site.portal_default_plan = value;
} else if (key === 'transistorPortalSettings' && value) {
data.site.transistor_portal_settings = JSON.parse(value);
data.site.transistor_portal_settings = safeJSONParse(value);
}
}
data.site.portal_plans = allowedPlans;
Expand Down Expand Up @@ -762,25 +770,30 @@ export default class App extends React.Component {

/**Handle state update for preview url and Portal Link changes */
updateStateForPreviewLinks() {
const {site: previewSite, ...restPreviewData} = this.fetchPreviewData();
const {site: linkSite, ...restLinkData} = this.fetchLinkData(this.state.site, this.state.member);

const updatedState = {
site: {
...this.state.site,
...(linkSite || {}),
...(previewSite || {}),
plans: {
...(this.state.site && this.state.site.plans),
...(linkSite || {}).plans,
...(previewSite || {}).plans
}
},
...restLinkData,
...restPreviewData
};
this.handleSignupQuery({site: updatedState.site, pageQuery: updatedState.pageQuery});
this.setState(updatedState);
try {
const {site: previewSite, ...restPreviewData} = this.fetchPreviewData();
const {site: linkSite, ...restLinkData} = this.fetchLinkData(this.state.site, this.state.member);

const updatedState = {
site: {
...this.state.site,
...(linkSite || {}),
...(previewSite || {}),
plans: {
...(this.state.site && this.state.site.plans),
...(linkSite || {}).plans,
...(previewSite || {}).plans
}
},
...restLinkData,
...restPreviewData
};
this.handleSignupQuery({site: updatedState.site, pageQuery: updatedState.pageQuery});
this.setState(updatedState);
} catch (error) {
// eslint-disable-next-line no-console
console.warn('[Portal] Failed to update preview links:', error);
}
}

/** Handle Portal offer urls */
Expand Down
Loading