@aion0/forge 0.10.76 → 0.10.77
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.
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# Forge v0.10.
|
|
1
|
+
# Forge v0.10.77
|
|
2
2
|
|
|
3
3
|
Released: 2026-06-12
|
|
4
4
|
|
|
5
|
-
## Changes since v0.10.
|
|
5
|
+
## Changes since v0.10.76
|
|
6
6
|
|
|
7
7
|
### Other
|
|
8
|
-
-
|
|
9
|
-
-
|
|
8
|
+
- fix(ui): route idpManualUrls open through openPortal too
|
|
9
|
+
- feat(ui): Open-portal buttons route through container Chromium when present
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.
|
|
12
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.76...v0.10.77
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
20
|
+
import { openPortal } from '@/lib/ui/openPortal';
|
|
20
21
|
|
|
21
22
|
interface MarketEntry {
|
|
22
23
|
id: string;
|
|
@@ -929,9 +930,8 @@ function TemplateImportModal({
|
|
|
929
930
|
{p.url && (
|
|
930
931
|
<a
|
|
931
932
|
href={p.url}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
className="ml-auto text-[10px] text-[var(--accent)] hover:underline"
|
|
933
|
+
onClick={(e) => { e.preventDefault(); void openPortal(p.url!); }}
|
|
934
|
+
className="ml-auto text-[10px] text-[var(--accent)] hover:underline cursor-pointer"
|
|
935
935
|
title={p.url}
|
|
936
936
|
>
|
|
937
937
|
↗ {p.url_label || 'Get token'}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { useEffect, useRef, useState } from 'react';
|
|
19
|
+
import { openPortal } from '@/lib/ui/openPortal';
|
|
19
20
|
|
|
20
21
|
interface SourceView {
|
|
21
22
|
tenant_id: string;
|
|
@@ -171,7 +172,7 @@ export default function EnterpriseBadge({ onOpenSettings: _onOpenSettings }: { o
|
|
|
171
172
|
r.source.refresh.url,
|
|
172
173
|
);
|
|
173
174
|
failed.forEach((r, i) => setTimeout(() => {
|
|
174
|
-
|
|
175
|
+
void openPortal(r.source.refresh.url!);
|
|
175
176
|
}, i * 250));
|
|
176
177
|
}
|
|
177
178
|
} catch (e) {
|
|
@@ -183,7 +184,7 @@ export default function EnterpriseBadge({ onOpenSettings: _onOpenSettings }: { o
|
|
|
183
184
|
|
|
184
185
|
const handleOpenWeb = (row: { refresh: { kind: string; url?: string } }) => {
|
|
185
186
|
if (row.refresh.kind === 'open-url' && row.refresh.url) {
|
|
186
|
-
|
|
187
|
+
void openPortal(row.refresh.url);
|
|
187
188
|
}
|
|
188
189
|
};
|
|
189
190
|
|
|
@@ -533,10 +534,9 @@ export default function EnterpriseBadge({ onOpenSettings: _onOpenSettings }: { o
|
|
|
533
534
|
<div key={i} className="flex items-center gap-1">
|
|
534
535
|
<a
|
|
535
536
|
href={u.url}
|
|
536
|
-
|
|
537
|
-
rel="noopener"
|
|
537
|
+
onClick={(e) => { e.preventDefault(); void openPortal(u.url); }}
|
|
538
538
|
title={u.error || u.host}
|
|
539
|
-
className="flex-1 bg-black/30 px-1.5 py-0.5 rounded font-mono text-[10px] break-all hover:bg-black/40 underline"
|
|
539
|
+
className="flex-1 bg-black/30 px-1.5 py-0.5 rounded font-mono text-[10px] break-all hover:bg-black/40 underline cursor-pointer"
|
|
540
540
|
>
|
|
541
541
|
{u.url}
|
|
542
542
|
</a>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback } from 'react';
|
|
4
|
+
import { openPortal } from '@/lib/ui/openPortal';
|
|
4
5
|
|
|
5
6
|
type LoginCategory = 'browser' | 'token' | 'external';
|
|
6
7
|
|
|
@@ -104,7 +105,7 @@ export default function LoginStatusPanel({ onClose }: { onClose: () => void }) {
|
|
|
104
105
|
const refresh = (source: LoginSource) => {
|
|
105
106
|
const r = source.refresh;
|
|
106
107
|
if (r.kind === 'open-url') {
|
|
107
|
-
|
|
108
|
+
void openPortal(r.url);
|
|
108
109
|
} else if (r.kind === 'show-command') {
|
|
109
110
|
setShowCmd({ command: r.command, description: r.description });
|
|
110
111
|
} else if (r.kind === 'open-settings') {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { useEffect, useRef, useState } from 'react';
|
|
17
|
+
import { openPortal } from '@/lib/ui/openPortal';
|
|
17
18
|
|
|
18
19
|
// ─── Types echoing the API surface ───────────────────────────
|
|
19
20
|
|
|
@@ -746,9 +747,8 @@ export function OnboardingDrawer({ onClose, onComplete, initialSourceId }: { onC
|
|
|
746
747
|
{!ok && ref?.kind === 'open-url' && ref.url && (
|
|
747
748
|
<a
|
|
748
749
|
href={ref.url}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
className="text-[var(--accent)] hover:underline ml-auto"
|
|
750
|
+
onClick={(e) => { e.preventDefault(); void openPortal(ref.url!); }}
|
|
751
|
+
className="text-[var(--accent)] hover:underline ml-auto cursor-pointer"
|
|
752
752
|
title={ref.description || ref.url}
|
|
753
753
|
>
|
|
754
754
|
↗ login
|
|
@@ -1242,7 +1242,7 @@ export function OnboardingDrawer({ onClose, onComplete, initialSourceId }: { onC
|
|
|
1242
1242
|
{!p.required && !isSet && <span className="text-[9px] text-[var(--text-secondary)]">(optional)</span>}
|
|
1243
1243
|
{isSet && <span className="text-[9px] text-emerald-500">● currently set</span>}
|
|
1244
1244
|
{p.url && (
|
|
1245
|
-
<a href={p.url}
|
|
1245
|
+
<a href={p.url} onClick={(e) => { e.preventDefault(); void openPortal(p.url!); }} className="ml-auto text-[10px] text-[var(--accent)] hover:underline cursor-pointer" title={p.url}>
|
|
1246
1246
|
↗ {p.url_label || 'Get token'}
|
|
1247
1247
|
</a>
|
|
1248
1248
|
)}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* openPortal — open a vendor login / portal URL in the RIGHT browser.
|
|
3
|
+
*
|
|
4
|
+
* Mac-native Forge: the UI runs in the user's own browser, which holds
|
|
5
|
+
* their SSO cookies → window.open is correct.
|
|
6
|
+
*
|
|
7
|
+
* Container deploy: the UI renders in the user's browser but the agent
|
|
8
|
+
* drives a Chromium INSIDE the container, where all SSO cookies live.
|
|
9
|
+
* window.open would land in the host browser (no corp session, useless).
|
|
10
|
+
* So when an extension is connected — the same signal the Login-status
|
|
11
|
+
* probe already relies on — route the open through the bridge so the
|
|
12
|
+
* extension runs chrome.tabs.create in the container Chromium.
|
|
13
|
+
*
|
|
14
|
+
* Pure client helper (fetch + DOM). Reference/help links should keep
|
|
15
|
+
* using a plain window.open / <a target="_blank"> — they genuinely
|
|
16
|
+
* belong in the user's own browser.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
function portalToast(msg: string): void {
|
|
20
|
+
if (typeof document === 'undefined') return;
|
|
21
|
+
const el = document.createElement('div');
|
|
22
|
+
el.textContent = msg;
|
|
23
|
+
el.style.cssText =
|
|
24
|
+
'position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:99999;' +
|
|
25
|
+
'background:#1e293b;color:#fff;padding:8px 16px;border-radius:8px;font-size:13px;' +
|
|
26
|
+
'box-shadow:0 4px 12px rgba(0,0,0,.3);opacity:0;transition:opacity .2s';
|
|
27
|
+
document.body.appendChild(el);
|
|
28
|
+
requestAnimationFrame(() => { el.style.opacity = '1'; });
|
|
29
|
+
setTimeout(() => { el.style.opacity = '0'; setTimeout(() => el.remove(), 300); }, 3000);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function openPortal(url: string): Promise<void> {
|
|
33
|
+
if (!url) return;
|
|
34
|
+
try {
|
|
35
|
+
const status = await fetch('/api/browser-bridge?action=status')
|
|
36
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
37
|
+
.catch(() => null);
|
|
38
|
+
const connected = Number(status?.connected_extensions || 0) > 0;
|
|
39
|
+
if (connected) {
|
|
40
|
+
const r = await fetch('/api/browser-bridge', {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'content-type': 'application/json' },
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
action: 'rpc',
|
|
45
|
+
method: 'browser.open_tab',
|
|
46
|
+
params: { url, active: true },
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
// proxy returns the bridge rpc envelope: {ok, value} | {ok:false, error}
|
|
50
|
+
const j = await r.json().catch(() => null);
|
|
51
|
+
if (r.ok && j && j.ok !== false) {
|
|
52
|
+
portalToast('Opened in workspace browser (visible via stream)');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
/* fall through to host-browser open */
|
|
58
|
+
}
|
|
59
|
+
window.open(url, '_blank', 'noopener');
|
|
60
|
+
}
|
package/package.json
CHANGED