Skip to content
Open
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions apps/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ <h2>Try Example Links</h2>
<span class="link-icon">📷</span>
<span class="link-text" style="padding-top: 8px;">Instagram Profile</span>
</a>
<a href="#" class="example-link x-link" data-url="https://x.com/">
<span class="link-icon">🆇</span>
<span class="link-text">X Profile</span>
</a>
Comment on lines +38 to +41

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

The demo URL is incomplete and won't match the handler regex.

The X Profile example link uses data-url="https://x.com/" which is missing a username. The xHandler regex in packages/core/src/platforms/x.ts (Line 4) expects a username: /https?:\/\/(?:www\.)?x\.com\/([^/?]+)/. This URL will fail to match and won't generate deep links when clicked in the demo.

🔎 Proposed fix: add a sample username
-          <a href="#" class="example-link x-link" data-url="https://x.com/">
+          <a href="#" class="example-link x-link" data-url="https://x.com/elonmusk">
             <span class="link-icon">🆇</span>
             <span class="link-text">X Profile</span>
           </a>
📝 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
<a href="#" class="example-link x-link" data-url="https://x.com/">
<span class="link-icon">🆇</span>
<span class="link-text">X Profile</span>
</a>
<a href="#" class="example-link x-link" data-url="https://x.com/elonmusk">
<span class="link-icon">🆇</span>
<span class="link-text">X Profile</span>
</a>
🤖 Prompt for AI Agents
In apps/demo/index.html around lines 38 to 41, the X Profile example uses
data-url="https://x.com/" which lacks a username and therefore won't match the
xHandler regex; update the data-url to include a sample username (e.g.
data-url="https://x.com/sampleuser") so the regex
/https?:\/\/(?:www\.)?x\.com\/([^/?]+)/ will match and deep links are generated
when the demo link is clicked.

</div>
</div>

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.50.0",
"@typescript-eslint/parser": "^8.50.0",
"eslint": "^9.39.2",
"eslint": "^8.57.1",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat package.json | head -50

Repository: mdsaban/universal-app-opener

Length of output: 909


Justify the ESLint version downgrade to ^8.57.1.

The version range of ESLint currently supported by @typescript-eslint is ^8.57.0 || ^9.0.0. The downgrade from ^9.39.2 to ^8.57.1 is not required for compatibility with @typescript-eslint v8.50.0 and appears unrelated to adding X deep link support. If this change is intentional (e.g., due to configuration migration constraints or other reasons), please document the justification in the PR description.

🤖 Prompt for AI Agents
In package.json around line 15, the ESLint version was downgraded to "eslint":
"^8.57.1" without justification; either revert the package.json change back to
the previous version (e.g., ^9.39.2) to preserve compatibility, or keep the
downgrade but add a clear justification to the PR description explaining why
^8.57.1 is required (compatibility constraint, config migration, CI/tooling
reasons, etc.); update the package.json only if necessary to match the justified
version and ensure it remains compatible with @typescript-eslint's supported
range.

"eslint-config-prettier": "^10.1.8",
"husky": "^9.1.7",
"lint-staged": "^16.2.7",
Expand Down
23 changes: 10 additions & 13 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { instagramHandler, linkedinHandler, unknownHandler, youtubeHandler } from "./platforms";
import { DeepLinkResult } from "./types";
import {
instagramHandler,
linkedinHandler,
unknownHandler,
youtubeHandler,
xHandler,
} from './platforms';
import { DeepLinkResult } from './types';

export * from './types';

const handlers = [
youtubeHandler,
linkedinHandler,
instagramHandler
];
const handlers = [youtubeHandler, linkedinHandler, instagramHandler, xHandler];
export function generateDeepLink(url: string): DeepLinkResult {
const webUrl = url.trim();

Expand Down Expand Up @@ -46,11 +48,7 @@ export interface OpenLinkOptions {
}

export function openLink(url: string, options: OpenLinkOptions = {}): void {
const {
fallbackToWeb = true,
fallbackDelay = 2500,
openInNewTab = false
} = options;
const { fallbackToWeb = true, fallbackDelay = 2500, openInNewTab = false } = options;

const os = detectOS();
const result = generateDeepLink(url);
Expand Down Expand Up @@ -83,4 +81,3 @@ export function openLink(url: string, options: OpenLinkOptions = {}): void {
}
}
}

16 changes: 6 additions & 10 deletions packages/core/src/platforms/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { linkedinHandler } from "./linkedin";
import { unknownHandler } from "./unknown";
import { youtubeHandler } from "./youtube";
import { instagramHandler } from "./instagram";
import { linkedinHandler } from './linkedin';
import { unknownHandler } from './unknown';
import { youtubeHandler } from './youtube';
import { instagramHandler } from './instagram';
import { xHandler } from './x';

export {
linkedinHandler,
youtubeHandler,
instagramHandler,
unknownHandler
}
export { linkedinHandler, youtubeHandler, instagramHandler, unknownHandler, xHandler };
16 changes: 16 additions & 0 deletions packages/core/src/platforms/x.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DeepLinkHandler } from '../types';

export const xHandler: DeepLinkHandler = {
match: (url) => url.match(/https?:\/\/(?:www\.)?x\.com\/([^/?]+)/),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's run the provided test script
node << 'EOF'
const regex = /https?:\/\/(?:www\.)?x\.com\/([^/?]+)/;

const testUrls = [
  "https://x.com/elonmusk",
  "https://www.x.com/elonmusk",
  "http://x.com/elonmusk",
  "https://x.com/elonmusk/",
  "https://x.com/elonmusk?some=param",
  "https://x.com/elonmusk/status/123456",
  "https://x.com/i/flow/login",
  "https://x.com/home",
  "https://x.com/explore"
];

console.log("Testing X URL regex pattern:\n");
testUrls.forEach(url => {
  const match = url.match(regex);
  console.log(`URL: ${url}`);
  console.log(`Match: ${match ? match[1] : 'NO MATCH'}\n`);
});
EOF

Repository: mdsaban/universal-app-opener

Length of output: 522


🏁 Script executed:

# Then check the file structure and location
find . -name "*.ts" -type f | grep -i "platform.*x\|x.*platform" | head -20

Repository: mdsaban/universal-app-opener

Length of output: 145


🏁 Script executed:

cat -n ./packages/core/src/platforms/x.ts

Repository: mdsaban/universal-app-opener

Length of output: 618


Ensure the regex pattern restricts to X profile URLs only.

The current regex /https?:\/\/(?:www\.)?x\.com\/([^/?]+)/ is too permissive. It matches and extracts values from non-profile URLs like x.com/i/flow/login, x.com/home, and x.com/explore, returning "i", "home", and "explore" respectively as usernames. These invalid values are then passed to the deep link builder, likely causing failures.

Since X usernames are limited to 1–15 characters and contain only letters, numbers, and underscores, tighten the pattern to /https?:\/\/(?:www\.)?x\.com\/([a-zA-Z0-9_]+)(?:\/|$|\?)/ or add a negative lookahead to exclude known non-profile paths like /status/, /i/, /home, /explore, etc.

🤖 Prompt for AI Agents
In packages/core/src/platforms/x.ts around line 4, tighten the URL matcher so it
only captures valid X profile usernames: restrict the captured segment to 1–15
characters consisting of letters, digits and underscores, and require a trailing
slash, end-of-string or query delimiter (or alternatively add a negative
lookahead to exclude known non-profile segments like "i", "home", "explore",
"status", etc.); update the match implementation accordingly so non-profile
paths no longer produce a fake username.


build: (webUrl, match) => {
const username = match[1];

return {
webUrl,
ios: `x://user?screen_name=${username}`,
android: `intent://user?screen_name=${username}#Intent;scheme=x;package=com.twitter.android;end`,
platform: 'x',
};
},
};
4 changes: 2 additions & 2 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type Platform = 'youtube' | 'linkedin' | 'instagram' | 'unknown';
export type Platform = 'youtube' | 'linkedin' | 'instagram' | 'unknown' | 'x';

export interface DeepLinkResult {
webUrl: string;
Expand All @@ -10,4 +10,4 @@ export interface DeepLinkResult {
export interface DeepLinkHandler {
match(url: string): RegExpMatchArray | null;
build(url: string, match: RegExpMatchArray): DeepLinkResult;
}
}
Loading