@bhooai/nexus-safe-goto 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +15 -0
  2. package/src/index.tsx +173 -0
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@bhooai/nexus-safe-goto",
3
+ "version": "1.0.0",
4
+ "description": "Safe external link navigation for the BhooAI Nexus admin panel — intercepts external URLs, shows a confirmation dialog, and opens with noopener/noreferrer to prevent tab-nabbing and phishing.",
5
+ "type": "module",
6
+ "main": "src/index.tsx",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./src/index.tsx"
10
+ }
11
+ },
12
+ "dependencies": {
13
+ "react": "^18.0.0"
14
+ }
15
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,173 @@
1
+ /**
2
+ * SafeGoto — secure external link navigation for the admin panel.
3
+ *
4
+ * External links are intercepted and routed through a confirmation dialog.
5
+ * On confirm, the URL is opened via `window.open` with `noopener,noreferrer`
6
+ * to prevent tab-nabbing (the opened page can't access `window.opener`) and
7
+ * referrer leakage (the opened page can't see the admin panel URL).
8
+ *
9
+ * Usage:
10
+ * // 1. Mount the dialog once at the app root:
11
+ * <SafeGotoDialog />
12
+ *
13
+ * // 2a. Use the hook component:
14
+ * <SafeGotoLink href="https://openai.com">OpenAI</SafeGotoLink>
15
+ *
16
+ * // 2b. Or trigger programmatically:
17
+ * safeGoto('https://openai.com');
18
+ */
19
+
20
+ import { useCallback, useEffect, useState } from 'react';
21
+
22
+ export interface SafeGotoOptions {
23
+ /** Label shown in the dialog (defaults to the hostname). */
24
+ label?: string;
25
+ /** Optional message override. */
26
+ message?: string;
27
+ }
28
+
29
+ interface PendingNav {
30
+ url: string;
31
+ label: string;
32
+ message?: string;
33
+ }
34
+
35
+ let mountedSetter: ((nav: PendingNav | null) => void) | null = null;
36
+
37
+ /**
38
+ * Register a setter so `safeGoto()` can trigger the mounted dialog.
39
+ * Called internally by `<SafeGotoDialog />` on mount.
40
+ */
41
+ export function registerSafeGoto(setter: (nav: PendingNav | null) => void): void {
42
+ mountedSetter = setter;
43
+ }
44
+
45
+ export function unregisterSafeGoto(): void {
46
+ mountedSetter = null;
47
+ }
48
+
49
+ /**
50
+ * Trigger the safe-goto confirmation dialog for an external URL.
51
+ * No-op if the dialog isn't mounted.
52
+ */
53
+ export function safeGoto(url: string, opts: SafeGotoOptions = {}): void {
54
+ if (!mountedSetter) {
55
+ // Fallback: open directly with safe params.
56
+ window.open(url, '_blank', 'noopener,noreferrer');
57
+ return;
58
+ }
59
+ const label = opts.label ?? hostnameOf(url);
60
+ mountedSetter({ url, label, message: opts.message });
61
+ }
62
+
63
+ /** Open a URL safely — always noopener + noreferrer. */
64
+ export function openSafe(url: string): void {
65
+ window.open(url, '_blank', 'noopener,noreferrer');
66
+ }
67
+
68
+ /** Extract the hostname from a URL for display. */
69
+ function hostnameOf(url: string): string {
70
+ try { return new URL(url).hostname; } catch { return url; }
71
+ }
72
+
73
+ /** Check if a URL is an external http(s) link (different origin from the current page). */
74
+ export function isExternalUrl(url: string): boolean {
75
+ try {
76
+ if (!/^https?:\/\//i.test(url)) return false;
77
+ const u = new URL(url, window.location.href);
78
+ return u.origin !== window.location.origin;
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Intercept clicks on `<a>` elements that point to external URLs.
86
+ * Returns a click handler that calls `safeGoto` for external links
87
+ * and lets internal links through.
88
+ *
89
+ * Usage:
90
+ * <div onClick={interceptExternalLinks}>...content with <a> tags...</div>
91
+ */
92
+ export function interceptExternalLinks(e: React.MouseEvent): void {
93
+ const target = (e.target as HTMLElement)?.closest('a');
94
+ if (!target) return;
95
+ const href = target.getAttribute('href');
96
+ if (!href) return;
97
+ // Only intercept http(s) external links.
98
+ if (!href.startsWith('http://') && !href.startsWith('https://')) return;
99
+ if (!isExternalUrl(href)) return;
100
+ e.preventDefault();
101
+ safeGoto(href);
102
+ }
103
+
104
+ /**
105
+ * The confirmation dialog component. Mount this once at the app root.
106
+ * It listens for `safeGoto()` calls and shows a modal before opening the link.
107
+ */
108
+ export function SafeGotoDialog() {
109
+ const [pending, setPending] = useState<PendingNav | null>(null);
110
+
111
+ useEffect(() => {
112
+ registerSafeGoto(setPending);
113
+ return () => unregisterSafeGoto();
114
+ }, []);
115
+
116
+ const cancel = useCallback(() => setPending(null), []);
117
+
118
+ const proceed = useCallback(() => {
119
+ if (pending) openSafe(pending.url);
120
+ setPending(null);
121
+ }, [pending]);
122
+
123
+ if (!pending) return null;
124
+
125
+ const url = pending.url;
126
+ const hostname = hostnameOf(url);
127
+
128
+ return (
129
+ <div className="safe-goto-overlay" onClick={cancel}>
130
+ <div className="safe-goto-dialog" onClick={(e) => e.stopPropagation()}>
131
+ <div className="safe-goto-icon-wrap">
132
+ <span className="safe-goto-icon">🛡️</span>
133
+ </div>
134
+ <div className="safe-goto-body">
135
+ <div className="safe-goto-title">Leaving BhooAI Nexus</div>
136
+ <p className="safe-goto-msg">
137
+ {pending.message ?? `You are about to visit an external website. We cannot guarantee the safety of content on third-party sites. Please verify the URL before proceeding.`}
138
+ </p>
139
+ <div className="safe-goto-url">
140
+ <span className="safe-goto-shield">🔗</span>
141
+ <code>{url}</code>
142
+ </div>
143
+ <div className="safe-goto-warning">
144
+ <span>⚠️</span>
145
+ <span>This link opens in a new tab with security protections enabled (noopener + noreferrer). Never enter your BhooAI credentials on external sites.</span>
146
+ </div>
147
+ <div className="safe-goto-actions">
148
+ <button onClick={cancel} className="glass-chip-btn">Stay here</button>
149
+ <button onClick={proceed} className="glass-btn-primary">Continue to {hostname} →</button>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ );
155
+ }
156
+
157
+ /**
158
+ * A safe external link component. Renders an `<a>` that triggers the
159
+ * confirmation dialog instead of navigating directly.
160
+ */
161
+ export function SafeGotoLink({ href, children, className }: { href: string; children: React.ReactNode; className?: string }) {
162
+ const handleClick = (e: React.MouseEvent) => {
163
+ e.preventDefault();
164
+ safeGoto(href);
165
+ };
166
+ return (
167
+ <a href={href} onClick={handleClick} className={className ?? 'safe-goto-link'} title={`Open ${hostnameOf(href)} in a new tab (safe)`}>
168
+ {children}
169
+ </a>
170
+ );
171
+ }
172
+
173
+ export default SafeGotoDialog;