gitdataai/src/lib/link-unfurl.ts
ZhenYi db0a2eca16 feat(ssh): add complete SSH server implementation for Git operations
- Implement SSHandle struct with comprehensive Git service handling capabilities
- Add support for multiple authentication methods including password, public key and certificate
- Integrate Git command parsing and execution with proper channel management
- Implement branch protection rules enforcement during Git operations
- Add robust error handling and logging for SSH connections and Git processes
- Create secure Git command execution with environment isolation
- Implement proper resource cleanup for channels and subprocesses
- Add support for receive-pack, upload-pack and upload-archive services
- Integrate with existing authz and database services for permission checks
- Implement proper data forwarding between SSH channels and Git processes

fix(config): improve environment loading with error reporting

- Replace silent dotenv loading failures with informative error messages
- Handle global config race conditions safely during application startup
- Improve config loading reliability and debugging capabilities

fix(link-unfurl): handle server-side rendering compatibility

- Add undefined window object check for SSR environments
- Prevent client-side only code from breaking server-side rendering

refactor(agent): improve tool registry error handling

- Replace panics with graceful error logging for duplicate tool registrations
- Add proper error type definitions for tool registry operations
- Implement safe merging of registries with duplicate detection

fix(room-context): enhance WebSocket connection reliability

- Add proper error handling for room subscription operations
- Improve connection management with better error suppression
- Add console warnings for debugging connection issues

feat(ws-client): add comprehensive WebSocket client implementation

- Create RoomWsClient class with complete WebSocket communication layer
- Implement request-response pattern with timeout handling
- Add support for various room-related events and actions
- Include proper connection status tracking and management
- Implement callback system for different event types
- Add automatic reconnection and error recovery mechanisms
2026-04-28 21:29:34 +08:00

145 lines
3.7 KiB
TypeScript

/**
* URL pattern detection and unfurling for smart link previews.
* Supports internal URLs (issues, PRs, commits, repos) and external URLs.
*/
export type LinkType =
| 'issue'
| 'pull_request'
| 'commit'
| 'repository'
| 'room'
| 'project'
| 'external';
export interface UnfurlResult {
type: LinkType;
url: string;
/** Parsed entity ID fields */
ids: {
project?: string;
namespace?: string;
repo?: string;
issueNumber?: number;
prNumber?: number;
commitOid?: string;
roomId?: string;
};
/** Display title (populated after fetch) */
title?: string;
/** Extra metadata (populated after fetch) */
meta?: Record<string, unknown>;
/** Whether the URL is external */
isExternal?: boolean;
/** Domain for external URLs */
domain?: string;
}
/** Internal URL patterns */
const INTERNAL_PATTERNS: Array<{
type: LinkType;
regex: RegExp;
extract: (match: RegExpMatchArray) => UnfurlResult['ids'];
}> = [
{
type: 'issue',
regex: /^\/project\/([^/]+)\/issues\/(\d+)/,
extract: (m) => ({ project: m[1], issueNumber: parseInt(m[2], 10) }),
},
{
type: 'pull_request',
regex: /^\/repository\/([^/]+)\/([^/]+)\/pulls?\/(\d+)/,
extract: (m) => ({ namespace: m[1], repo: m[2], prNumber: parseInt(m[3], 10) }),
},
{
type: 'commit',
regex: /^\/repository\/([^/]+)\/([^/]+)\/commit\/([a-f0-9]+)/,
extract: (m) => ({ namespace: m[1], repo: m[2], commitOid: m[3] }),
},
{
type: 'repository',
regex: /^\/repository\/([^/]+)\/([^/]+)/,
extract: (m) => ({ namespace: m[1], repo: m[2] }),
},
{
type: 'project',
regex: /^\/project\/([^/]+)/,
extract: (m) => ({ project: m[1] }),
},
{
type: 'room',
regex: /^\/project\/([^/]+)\/room(?:\/([^/?#]+))?/,
extract: (m) => ({ project: m[1], roomId: m[2] }),
},
];
/** Detect the type and IDs of a URL */
export function detectLinkType(url: string): UnfurlResult | null {
// Remove trailing slashes and hash
const normalized = url.split('?')[0].split('#')[0].replace(/\/+$/, '');
// Check internal patterns
for (const pattern of INTERNAL_PATTERNS) {
const match = normalized.match(pattern.regex);
if (match) {
return {
type: pattern.type,
url: `/${normalized.replace(/^\//, '')}`,
ids: pattern.extract(match),
};
}
}
// External URL
try {
const parsed = new URL(url);
const isExternal = typeof window === 'undefined'
? true
: !parsed.hostname.includes(window.location.hostname);
return {
type: 'external',
url,
ids: {},
isExternal,
domain: parsed.hostname,
};
} catch {
return null;
}
}
/** Extract all URLs from a text string */
export function extractUrls(text: string): Array<{ url: string; index: number }> {
// Match URLs (including internal paths starting with /)
const urlPattern = /(?:https?:\/\/[^\s<>"]+|^\/[^\s<>"]+|\/[^\s<>"]+)/gm;
const results: Array<{ url: string; index: number }> = [];
let match;
while ((match = urlPattern.exec(text)) !== null) {
const url = match[0];
// Filter out very short or malformed URLs
if (url.length > 3) {
results.push({ url, index: match.index });
}
}
return results;
}
/** Simple cache for unfurl results */
const unfurlCache = new Map<string, UnfurlResult & { expiresAt: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
export function getCachedUnfurl(url: string): (UnfurlResult & { expiresAt: number }) | undefined {
const cached = unfurlCache.get(url);
if (cached && cached.expiresAt > Date.now()) {
return cached;
}
return undefined;
}
export function setCachedUnfurl(url: string, result: UnfurlResult): void {
unfurlCache.set(url, { ...result, expiresAt: Date.now() + CACHE_TTL });
}