Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
00fac1e
first steps
Oct 20, 2025
9036817
user login
Oct 20, 2025
45b7c3a
app works
Oct 20, 2025
9e9ac8c
sub info
Oct 20, 2025
ad6b64e
sub info
Oct 20, 2025
a719068
fixes
Oct 20, 2025
626d676
switch to paddle js
Oct 21, 2025
f3b4411
refactor
Oct 21, 2025
b621e98
rfeactor
Oct 21, 2025
0126758
overlay
Oct 21, 2025
b1ebae0
style
Oct 21, 2025
27dd63c
disconnect
Oct 21, 2025
e4ca664
esm loading
Oct 23, 2025
a9fa836
component loading
Oct 23, 2025
f9068e2
Merge branch 'master' of https://github.com/remix-project-org/remix-p…
Nov 8, 2025
f470b91
sso endpoint
Nov 10, 2025
ab6d0c5
Merge branch 'master' of https://github.com/remix-project-org/remix-p…
Nov 13, 2025
651dd5a
basic sso
Nov 14, 2025
2c44eab
google works
Nov 14, 2025
540abbd
message
Nov 14, 2025
a87f77c
coinbase
Nov 14, 2025
1c6af09
discord
Nov 14, 2025
3539e4c
token check
Nov 14, 2025
5e3a25e
credits work
Nov 14, 2025
f90e5dc
siwe seamless
Nov 22, 2025
ec78602
popup logic
Nov 22, 2025
0360e34
broken
Nov 22, 2025
90ea430
logins fixed
Nov 23, 2025
052f3c8
translate providers
Nov 23, 2025
279c15c
mv the modal
Nov 23, 2025
299ef33
refactor
Nov 23, 2025
eedb8da
listeners
Nov 23, 2025
b0ae8e3
fix modal
Nov 23, 2025
2c063ad
user info update
Nov 23, 2025
068e72c
rm paddle parts
Nov 23, 2025
50a581f
more rm subscriptions
Nov 23, 2025
c0450e9
rm subs
Nov 23, 2025
7f27336
rm docs
Nov 23, 2025
b9d1cc0
tsconfig
Nov 23, 2025
82a04ff
lock file
Nov 23, 2025
c307014
sso demo remove
Nov 23, 2025
de04832
Merge branch 'master' of https://github.com/remix-project-org/remix-p…
Nov 23, 2025
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
24 changes: 23 additions & 1 deletion apps/remix-ide/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { EnvironmentExplorer } from './app/providers/environment-explorer'
import { FileDecorator } from './app/plugins/file-decorator'
import { CodeFormat } from './app/plugins/code-format'
import { CompilationDetailsPlugin } from './app/plugins/compile-details'
import { AuthPlugin } from './app/plugins/auth-plugin'
import { RemixGuidePlugin } from './app/plugins/remixGuide'
import { TemplatesPlugin } from './app/plugins/remix-templates'
import { fsPlugin } from './app/plugins/electron/fsPlugin'
Expand Down Expand Up @@ -85,6 +86,8 @@ import { QueryParams } from '@remix-project/remix-lib'
import { SearchPlugin } from './app/tabs/search'
import { ScriptRunnerBridgePlugin } from './app/plugins/script-runner-bridge'
import { ElectronProvider } from './app/files/electronProvider'
import { IframePlugin } from '@remixproject/engine-web'
import { endpointUrls } from '@remix-endpoints-helper'

const Storage = remixLib.Storage
import RemixDProvider from './app/files/remixDProvider'
Expand Down Expand Up @@ -160,6 +163,7 @@ class AppComponent {
topBar: Topbar
templateExplorerModal: TemplateExplorerModalPlugin
settings: SettingsTab
authPlugin: AuthPlugin
params: any
desktopClientMode: boolean

Expand Down Expand Up @@ -303,6 +307,17 @@ class AppComponent {
//---------------- Script Runner UI Plugin -------------------------
const scriptRunnerUI = new ScriptRunnerBridgePlugin(this.engine)

//---------------- SSO Plugin (Hidden Iframe) -------------------------
const ssoPlugin = new IframePlugin({
name: 'sso',
displayName: 'SSO Authentication',
url: `${endpointUrls.ssoPlugin}?ideOrigin=${encodeURIComponent(window.location.origin)}`,
location: 'hiddenPanel',
description: 'Manages authentication with OIDC providers and SIWE',
methods: ['login', 'logout', 'getUser', 'getToken', 'isAuthenticated', 'handlePopupResult'],
events: ['authStateChanged', 'tokenRefreshed', 'loginSuccess', 'logoutSuccess', 'openWindow', 'loginError']
})

//---- templates
const templates = new TemplatesPlugin()

Expand Down Expand Up @@ -560,6 +575,8 @@ class AppComponent {
contentImport
)

this.authPlugin = new AuthPlugin()

this.engine.register([
compileTab,
run,
Expand All @@ -571,7 +588,9 @@ class AppComponent {
linkLibraries,
deployLibraries,
openZeppelinProxy,
run.recorder
run.recorder,
this.authPlugin,
ssoPlugin
])
this.engine.register([templateExplorerModal, this.topBar])

Expand Down Expand Up @@ -636,6 +655,9 @@ class AppComponent {
'remixAI',
'remixaiassistant'
])
// Activate SSO plugin first, then Auth plugin (Auth depends on SSO)
await this.appManager.activatePlugin(['sso'])
await this.appManager.activatePlugin(['auth'])
await this.appManager.activatePlugin(['settings'])

await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder', 'dgitApi', 'dgit'])
Expand Down
226 changes: 226 additions & 0 deletions apps/remix-ide/src/app/plugins/auth-plugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import { Plugin } from '@remixproject/engine'
import { AuthUser, AuthProvider as AuthProviderType } from '@remix-api'

export interface Credits {
balance: number
free_credits: number
paid_credits: number
}

const profile = {
name: 'auth',
displayName: 'Authentication',
description: 'Handles SSO authentication and credits',
methods: ['login', 'logout', 'getUser', 'getCredits', 'refreshCredits'],
events: ['authStateChanged', 'creditsUpdated']
}

export class AuthPlugin extends Plugin {
constructor() {
super(profile)
}

async login(provider: AuthProviderType): Promise<void> {
try {
await this.call('sso', 'login', provider)
} catch (error) {
console.error('[AuthPlugin] Login failed:', error)
throw error
}
}

async logout(): Promise<void> {
try {
await this.call('sso', 'logout')
} catch (error) {
console.error('[AuthPlugin] Logout failed:', error)
}
}

async getUser(): Promise<AuthUser | null> {
try {
return await this.call('sso', 'getUser')
} catch (error) {
console.error('[AuthPlugin] Get user failed:', error)
return null
}
}

async isAuthenticated(): Promise<boolean> {
try {
return await this.call('sso', 'isAuthenticated')
} catch (error) {
return false
}
}

async getToken(): Promise<string | null> {
try {
return await this.call('sso', 'getToken')
} catch (error) {
return null
}
}

async getCredits(): Promise<Credits | null> {
const baseUrl = window.location.hostname.includes('localhost')
? 'http://localhost:3000'
: 'https://endpoints-remix-dev.ngrok.dev'

try {
const response = await fetch(`${baseUrl}/credits/balance`, {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'application/json' }
})

if (response.ok) {
return await response.json()
}
return null
} catch (error) {
console.error('[AuthPlugin] Failed to fetch credits:', error)
return null
}
}

async refreshCredits(): Promise<Credits | null> {
const credits = await this.getCredits()
if (credits) {
this.emit('creditsUpdated', credits)
}
return credits
}

onActivation(): void {
console.log('[AuthPlugin] Activated')

// Debug: Log queue status
setInterval(() => {
if ((this as any).queue && (this as any).queue.length > 0) {
console.log('[AuthPlugin] Queue:', (this as any).queue)
}
}, 2000)

// Listen to SSO plugin events and forward them
this.on('sso', 'authStateChanged', (authState: any) => {
console.log('[AuthPlugin] authStateChanged received:', authState)
this.emit('authStateChanged', authState)
// Auto-refresh credits on auth change
if (authState.isAuthenticated) {
this.refreshCredits().catch(console.error)
}
})

this.on('sso', 'loginSuccess', (data: any) => {
console.log('[AuthPlugin] loginSuccess received:', data)
this.emit('authStateChanged', {
isAuthenticated: true,
user: data.user,
token: null
})
this.refreshCredits().catch(console.error)
})

this.on('sso', 'loginError', (data: any) => {
console.log('[AuthPlugin] loginError received:', data)
this.emit('authStateChanged', {
isAuthenticated: false,
user: null,
token: null,
error: data.error
})
})

this.on('sso', 'logout', () => {
console.log('[AuthPlugin] logout received')
this.emit('authStateChanged', {
isAuthenticated: false,
user: null,
token: null
})
})

// Handle popup opening from SSO plugin
this.on('sso', 'openWindow', ({ url, id }: { url: string; id: string }) => {
console.log('[AuthPlugin] openWindow received:', url, id)
const width = 600
const height = 700
const left = window.screen.width / 2 - width / 2
const top = window.screen.height / 2 - height / 2

const popup = window.open(
url,
'sso-auth',
`width=${width},height=${height},left=${left},top=${top},toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes`
)

if (!popup) {
console.error('[AuthPlugin] Popup blocked by browser')
this.call('sso', 'handlePopupResult', {
id,
success: false,
error: 'Popup blocked by browser'
}).catch(console.error)
return
}

// Listen for auth result from popup
const messageHandler = (event: MessageEvent) => {
console.log('[AuthPlugin] Message received:', event.data, 'from origin:', event.origin)
const { type, requestId, user, accessToken, error } = event.data

if (type === 'sso-auth-result' && requestId === id) {
console.log('[AuthPlugin] Auth result matched, closing popup')
cleanup()

try {
if (popup && !popup.closed) popup.close()
} catch (e) {}

console.log('[AuthPlugin] Calling handlePopupResult with:', {id, success: !error, user, accessToken, error})
this.call('sso', 'handlePopupResult', {
id,
success: !error,
user,
accessToken,
error
}).then(() => {
console.log('[AuthPlugin] handlePopupResult call succeeded')
}).catch((err) => {
console.error('[AuthPlugin] handlePopupResult call failed:', err)
})
}
}

// Poll for popup closure
let pollAttempts = 0
const maxPollAttempts = 600
const pollInterval = setInterval(() => {
pollAttempts++

try {
if (popup.closed) {
cleanup()
this.call('sso', 'handlePopupResult', {
id,
success: false,
error: 'Login cancelled - popup closed'
}).catch(console.error)
}
} catch (e) {}

if (pollAttempts >= maxPollAttempts) {
cleanup()
}
}, 1000)

const cleanup = () => {
clearInterval(pollInterval)
window.removeEventListener('message', messageHandler)
}

window.addEventListener('message', messageHandler)
})
}
}
1 change: 1 addition & 0 deletions apps/remix-ide/src/app/utils/AppRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createRoot, Root } from 'react-dom/client';
import { TrackingProvider } from '../contexts/TrackingContext';
import { Preload } from '../components/preload';
import { GitHubPopupCallback } from '../pages/GitHubPopupCallback';
// Popup/standalone subscription pages removed; overlay/inline handled in main UI
import { TrackingFunction } from './TrackingFunction';

export interface RenderAppOptions {
Expand Down
4 changes: 3 additions & 1 deletion apps/remix-ide/src/remixAppManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ let requiredModules = [
'topbar',
'templateexplorermodal',
'githubAuthHandler',
'desktopClient'
'desktopClient',
'sso',
'auth'
]

// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
Expand Down
1 change: 1 addition & 0 deletions apps/remix-ide/src/remixEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class RemixEngine extends Engine {
if (name === 'contentImport') return { queueTimeout: 60000 * 3 }
if (name === 'circom') return { queueTimeout: 60000 * 4 }
if (name === 'noir-compiler') return { queueTimeout: 60000 * 4 }
if (name === 'sso') return { queueTimeout: 60000 * 4 }
return { queueTimeout: 10000 }
}

Expand Down
17 changes: 16 additions & 1 deletion apps/remix-ide/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
// set the define plugin to load the WALLET_CONNECT_PROJECT_ID
config.plugins.push(
new webpack.DefinePlugin({
WALLET_CONNECT_PROJECT_ID: JSON.stringify(process.env.WALLET_CONNECT_PROJECT_ID)
WALLET_CONNECT_PROJECT_ID: JSON.stringify(process.env.WALLET_CONNECT_PROJECT_ID),
'process.env.NX_ENDPOINTS_URL': JSON.stringify(process.env.NX_ENDPOINTS_URL)
})
)

Expand Down Expand Up @@ -244,6 +245,20 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
}

console.log('config', process.env.NX_DESKTOP_FROM_DIST)

// Dev-server settings: allow ngrok hostnames (avoids "Invalid Host header")
// This only affects `yarn serve` (webpack dev server), not production builds.
config.devServer = {
...(config.devServer || {}),
host: '0.0.0.0',
allowedHosts: 'all',
headers: {
...(config.devServer && config.devServer.headers ? config.devServer.headers : {}),
'ngrok-skip-browser-warning': '1',
},
historyApiFallback: true,
}

return config;
});

Expand Down
6 changes: 6 additions & 0 deletions libs/endpoints-helper/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type EndpointUrls = {
vyper2: string;
solidityScanWebSocket: string;
gitHubLoginProxy: string;
sso: string;
ssoPlugin: string;
};

const defaultUrls: EndpointUrls = {
Expand All @@ -32,6 +34,8 @@ const defaultUrls: EndpointUrls = {
completion: 'https://completion.api.remix.live',
solidityScanWebSocket: 'wss://solidityscan.api.remix.live',
gitHubLoginProxy: 'https://github-login-proxy.api.remix.live',
sso: 'https://sso.api.remix.live',
ssoPlugin: 'https://sso-plugin.api.remix.live',
};

const endpointPathMap: Record<keyof EndpointUrls, string> = {
Expand All @@ -50,6 +54,8 @@ const endpointPathMap: Record<keyof EndpointUrls, string> = {
vyper2: 'vyper2',
solidityScanWebSocket: '',
gitHubLoginProxy: 'github-login-proxy',
sso: 'sso',
ssoPlugin: 'sso-plugin',
};

const prefix = process.env.NX_ENDPOINTS_URL;
Expand Down
Loading