commons-proxy 2.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.
- package/LICENSE +21 -0
- package/README.md +757 -0
- package/bin/cli.js +146 -0
- package/package.json +97 -0
- package/public/Complaint Details.pdf +0 -0
- package/public/Cyber Crime Portal.pdf +0 -0
- package/public/app.js +229 -0
- package/public/css/src/input.css +523 -0
- package/public/css/style.css +1 -0
- package/public/favicon.png +0 -0
- package/public/index.html +549 -0
- package/public/js/components/account-manager.js +356 -0
- package/public/js/components/add-account-modal.js +414 -0
- package/public/js/components/claude-config.js +420 -0
- package/public/js/components/dashboard/charts.js +605 -0
- package/public/js/components/dashboard/filters.js +362 -0
- package/public/js/components/dashboard/stats.js +110 -0
- package/public/js/components/dashboard.js +236 -0
- package/public/js/components/logs-viewer.js +100 -0
- package/public/js/components/models.js +36 -0
- package/public/js/components/server-config.js +349 -0
- package/public/js/config/constants.js +102 -0
- package/public/js/data-store.js +375 -0
- package/public/js/settings-store.js +58 -0
- package/public/js/store.js +99 -0
- package/public/js/translations/en.js +367 -0
- package/public/js/translations/id.js +412 -0
- package/public/js/translations/pt.js +308 -0
- package/public/js/translations/tr.js +358 -0
- package/public/js/translations/zh.js +373 -0
- package/public/js/utils/account-actions.js +189 -0
- package/public/js/utils/error-handler.js +96 -0
- package/public/js/utils/model-config.js +42 -0
- package/public/js/utils/ui-logger.js +143 -0
- package/public/js/utils/validators.js +77 -0
- package/public/js/utils.js +69 -0
- package/public/proxy-server-64.png +0 -0
- package/public/views/accounts.html +361 -0
- package/public/views/dashboard.html +484 -0
- package/public/views/logs.html +97 -0
- package/public/views/models.html +331 -0
- package/public/views/settings.html +1327 -0
- package/src/account-manager/credentials.js +378 -0
- package/src/account-manager/index.js +462 -0
- package/src/account-manager/onboarding.js +112 -0
- package/src/account-manager/rate-limits.js +369 -0
- package/src/account-manager/storage.js +160 -0
- package/src/account-manager/strategies/base-strategy.js +109 -0
- package/src/account-manager/strategies/hybrid-strategy.js +339 -0
- package/src/account-manager/strategies/index.js +79 -0
- package/src/account-manager/strategies/round-robin-strategy.js +76 -0
- package/src/account-manager/strategies/sticky-strategy.js +138 -0
- package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
- package/src/account-manager/strategies/trackers/index.js +9 -0
- package/src/account-manager/strategies/trackers/quota-tracker.js +120 -0
- package/src/account-manager/strategies/trackers/token-bucket-tracker.js +155 -0
- package/src/auth/database.js +169 -0
- package/src/auth/oauth.js +548 -0
- package/src/auth/token-extractor.js +117 -0
- package/src/cli/accounts.js +648 -0
- package/src/cloudcode/index.js +29 -0
- package/src/cloudcode/message-handler.js +510 -0
- package/src/cloudcode/model-api.js +248 -0
- package/src/cloudcode/rate-limit-parser.js +235 -0
- package/src/cloudcode/request-builder.js +93 -0
- package/src/cloudcode/session-manager.js +47 -0
- package/src/cloudcode/sse-parser.js +121 -0
- package/src/cloudcode/sse-streamer.js +293 -0
- package/src/cloudcode/streaming-handler.js +615 -0
- package/src/config.js +125 -0
- package/src/constants.js +407 -0
- package/src/errors.js +242 -0
- package/src/fallback-config.js +29 -0
- package/src/format/content-converter.js +193 -0
- package/src/format/index.js +20 -0
- package/src/format/request-converter.js +255 -0
- package/src/format/response-converter.js +120 -0
- package/src/format/schema-sanitizer.js +673 -0
- package/src/format/signature-cache.js +88 -0
- package/src/format/thinking-utils.js +648 -0
- package/src/index.js +148 -0
- package/src/modules/usage-stats.js +205 -0
- package/src/providers/anthropic-provider.js +258 -0
- package/src/providers/base-provider.js +157 -0
- package/src/providers/cloudcode.js +94 -0
- package/src/providers/copilot.js +399 -0
- package/src/providers/github-provider.js +287 -0
- package/src/providers/google-provider.js +192 -0
- package/src/providers/index.js +211 -0
- package/src/providers/openai-compatible.js +265 -0
- package/src/providers/openai-provider.js +271 -0
- package/src/providers/openrouter-provider.js +325 -0
- package/src/providers/setup.js +83 -0
- package/src/server.js +870 -0
- package/src/utils/claude-config.js +245 -0
- package/src/utils/helpers.js +51 -0
- package/src/utils/logger.js +142 -0
- package/src/utils/native-module-helper.js +162 -0
- package/src/webui/index.js +1134 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
// Read package.json for version
|
|
11
|
+
const packageJson = JSON.parse(
|
|
12
|
+
readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const command = args[0];
|
|
17
|
+
|
|
18
|
+
function showHelp() {
|
|
19
|
+
console.log(`
|
|
20
|
+
CommonsProxy v${packageJson.version}
|
|
21
|
+
|
|
22
|
+
Universal proxy server for Claude Code CLI - supports multiple AI providers.
|
|
23
|
+
|
|
24
|
+
USAGE:
|
|
25
|
+
commons-proxy <command> [options]
|
|
26
|
+
|
|
27
|
+
COMMANDS:
|
|
28
|
+
start Start the proxy server (default port: 8080)
|
|
29
|
+
accounts Manage accounts (interactive)
|
|
30
|
+
accounts add Add a new account (prompts for provider)
|
|
31
|
+
accounts list List all configured accounts
|
|
32
|
+
accounts remove Remove accounts interactively
|
|
33
|
+
accounts verify Verify account tokens are valid
|
|
34
|
+
accounts clear Remove all accounts
|
|
35
|
+
|
|
36
|
+
OPTIONS:
|
|
37
|
+
--help, -h Show this help message
|
|
38
|
+
--version, -v Show version number
|
|
39
|
+
--strategy=<type> Account selection strategy (sticky|round-robin|hybrid)
|
|
40
|
+
--fallback Enable model fallback when quota exhausted
|
|
41
|
+
--debug Enable debug logging
|
|
42
|
+
--no-browser Use manual code input for OAuth (headless servers)
|
|
43
|
+
|
|
44
|
+
ENVIRONMENT:
|
|
45
|
+
PORT Server port (default: 8080)
|
|
46
|
+
WEBUI_PASSWORD Password protect WebUI (optional)
|
|
47
|
+
STRATEGY Account selection strategy (default: hybrid)
|
|
48
|
+
FALLBACK Enable model fallback (true|false)
|
|
49
|
+
DEBUG Enable debug logging (true|false)
|
|
50
|
+
|
|
51
|
+
PROVIDERS:
|
|
52
|
+
🔵 Google Cloud Code OAuth 2.0 flow for Claude & Gemini models
|
|
53
|
+
🟠 Anthropic Direct API access via API key
|
|
54
|
+
🟢 OpenAI GPT models via API key (supports Azure)
|
|
55
|
+
🟣 GitHub Models GitHub marketplace access via Personal Access Token
|
|
56
|
+
🟧 GitHub Copilot Device authorization flow for Copilot models
|
|
57
|
+
🟪 OpenRouter Unified API for 100+ models via API key
|
|
58
|
+
|
|
59
|
+
EXAMPLES:
|
|
60
|
+
# Start server with default settings
|
|
61
|
+
commons-proxy start
|
|
62
|
+
|
|
63
|
+
# Start with custom port and strategy
|
|
64
|
+
PORT=3000 commons-proxy start --strategy=round-robin
|
|
65
|
+
|
|
66
|
+
# Start with model fallback enabled
|
|
67
|
+
commons-proxy start --fallback --debug
|
|
68
|
+
|
|
69
|
+
# Add accounts from different providers
|
|
70
|
+
commons-proxy accounts add # Interactive (prompts for provider)
|
|
71
|
+
commons-proxy accounts add --no-browser # Manual OAuth code input
|
|
72
|
+
|
|
73
|
+
# List and verify accounts
|
|
74
|
+
commons-proxy accounts list
|
|
75
|
+
commons-proxy accounts verify
|
|
76
|
+
|
|
77
|
+
CONFIGURATION:
|
|
78
|
+
Claude Code CLI (~/.claude/settings.json):
|
|
79
|
+
{
|
|
80
|
+
"env": {
|
|
81
|
+
"ANTHROPIC_BASE_URL": "http://localhost:8080"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Web Interface:
|
|
86
|
+
http://localhost:8080 - Manage accounts, view stats, configure settings
|
|
87
|
+
|
|
88
|
+
DOCUMENTATION:
|
|
89
|
+
Getting Started: docs/PROVIDERS.md - Setup guides for all providers
|
|
90
|
+
Contributing: CONTRIBUTING.md - Developer onboarding guide
|
|
91
|
+
GitHub: https://github.com/AryanVBW/CommonsProxy
|
|
92
|
+
npm: https://www.npmjs.com/package/commons-proxy
|
|
93
|
+
`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function showVersion() {
|
|
97
|
+
console.log(packageJson.version);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function main() {
|
|
101
|
+
// Handle flags
|
|
102
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
103
|
+
showHelp();
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
108
|
+
showVersion();
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Handle commands
|
|
113
|
+
switch (command) {
|
|
114
|
+
case 'start':
|
|
115
|
+
case undefined:
|
|
116
|
+
// Default to starting the server
|
|
117
|
+
await import('../src/index.js');
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case 'accounts': {
|
|
121
|
+
// Pass remaining args to accounts CLI
|
|
122
|
+
const subCommand = args[1] || 'add';
|
|
123
|
+
process.argv = ['node', 'accounts-cli.js', subCommand, ...args.slice(2)];
|
|
124
|
+
await import('../src/cli/accounts.js');
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case 'help':
|
|
129
|
+
showHelp();
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
case 'version':
|
|
133
|
+
showVersion();
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
default:
|
|
137
|
+
console.error(`Unknown command: ${command}`);
|
|
138
|
+
console.error('Run "commons-proxy --help" for usage information.');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
main().catch((err) => {
|
|
144
|
+
console.error('Error:', err.message);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "commons-proxy",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Universal AI proxy server with multi-provider support (Google Cloud Code, Anthropic, OpenAI, GitHub Models, GitHub Copilot, OpenRouter) - Anthropic-compatible API for Claude Code CLI",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"commons-proxy": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"bin",
|
|
13
|
+
"public"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build:css": "tailwindcss -i ./public/css/src/input.css -o ./public/css/style.css --minify",
|
|
17
|
+
"watch:css": "tailwindcss -i ./public/css/src/input.css -o ./public/css/style.css --watch",
|
|
18
|
+
"prepare": "npm run build:css",
|
|
19
|
+
"prepare-release": "npm run build:css && npm test",
|
|
20
|
+
"start": "node src/index.js",
|
|
21
|
+
"dev": "node --watch src/index.js",
|
|
22
|
+
"dev:full": "concurrently \"npm run watch:css\" \"npm run dev\"",
|
|
23
|
+
"accounts": "node src/cli/accounts.js",
|
|
24
|
+
"accounts:add": "node src/cli/accounts.js add",
|
|
25
|
+
"accounts:list": "node src/cli/accounts.js list",
|
|
26
|
+
"accounts:remove": "node src/cli/accounts.js remove",
|
|
27
|
+
"accounts:verify": "node src/cli/accounts.js verify",
|
|
28
|
+
"test": "node tests/run-all.cjs",
|
|
29
|
+
"test:signatures": "node tests/test-thinking-signatures.cjs",
|
|
30
|
+
"test:multiturn": "node tests/test-multiturn-thinking-tools.cjs",
|
|
31
|
+
"test:streaming": "node tests/test-multiturn-thinking-tools-streaming.cjs",
|
|
32
|
+
"test:interleaved": "node tests/test-interleaved-thinking.cjs",
|
|
33
|
+
"test:images": "node tests/test-images.cjs",
|
|
34
|
+
"test:caching": "node tests/test-caching-streaming.cjs",
|
|
35
|
+
"test:crossmodel": "node tests/test-cross-model-thinking.cjs",
|
|
36
|
+
"test:oauth": "node tests/test-oauth-no-browser.cjs",
|
|
37
|
+
"test:emptyretry": "node tests/test-empty-response-retry.cjs",
|
|
38
|
+
"test:sanitizer": "node tests/test-schema-sanitizer.cjs",
|
|
39
|
+
"test:strategies": "node tests/test-strategies.cjs",
|
|
40
|
+
"test:cache-control": "node tests/test-cache-control.cjs"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"claude",
|
|
44
|
+
"anthropic",
|
|
45
|
+
"openai",
|
|
46
|
+
"github-models",
|
|
47
|
+
"github-copilot",
|
|
48
|
+
"openrouter",
|
|
49
|
+
"commons",
|
|
50
|
+
"ai-proxy",
|
|
51
|
+
"ai-gateway",
|
|
52
|
+
"multi-provider",
|
|
53
|
+
"llm-proxy",
|
|
54
|
+
"llm",
|
|
55
|
+
"copilot",
|
|
56
|
+
"proxy",
|
|
57
|
+
"vertex-ai",
|
|
58
|
+
"gemini",
|
|
59
|
+
"gpt",
|
|
60
|
+
"load-balancer",
|
|
61
|
+
"api-gateway"
|
|
62
|
+
],
|
|
63
|
+
"author": "Badri Narayanan S <badrinarayanans@gmail.com>",
|
|
64
|
+
"contributors": [
|
|
65
|
+
"AryanVBW"
|
|
66
|
+
],
|
|
67
|
+
"license": "MIT",
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "git+https://github.com/AryanVBW/CommonsProxy.git"
|
|
71
|
+
},
|
|
72
|
+
"homepage": "https://github.com/AryanVBW/CommonsProxy#readme",
|
|
73
|
+
"bugs": {
|
|
74
|
+
"url": "https://github.com/AryanVBW/CommonsProxy/issues"
|
|
75
|
+
},
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": ">=18.0.0"
|
|
78
|
+
},
|
|
79
|
+
"publishConfig": {
|
|
80
|
+
"access": "public",
|
|
81
|
+
"registry": "https://registry.npmjs.org/"
|
|
82
|
+
},
|
|
83
|
+
"dependencies": {
|
|
84
|
+
"async-mutex": "^0.5.0",
|
|
85
|
+
"better-sqlite3": "^12.5.0",
|
|
86
|
+
"cors": "^2.8.5",
|
|
87
|
+
"express": "^4.18.2"
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"@tailwindcss/forms": "^0.5.7",
|
|
91
|
+
"autoprefixer": "^10.4.16",
|
|
92
|
+
"concurrently": "^8.2.2",
|
|
93
|
+
"daisyui": "^4.12.14",
|
|
94
|
+
"postcss": "^8.4.32",
|
|
95
|
+
"tailwindcss": "^3.4.0"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
Binary file
|
|
Binary file
|
package/public/app.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity Console - Main Entry
|
|
3
|
+
*
|
|
4
|
+
* This file orchestrates Alpine.js initialization.
|
|
5
|
+
* Components are loaded via separate script files that register themselves
|
|
6
|
+
* to window.Components before this script runs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
document.addEventListener('alpine:init', () => {
|
|
10
|
+
// Register Components (loaded from separate files via window.Components)
|
|
11
|
+
Alpine.data('dashboard', window.Components.dashboard);
|
|
12
|
+
Alpine.data('models', window.Components.models);
|
|
13
|
+
Alpine.data('accountManager', window.Components.accountManager);
|
|
14
|
+
Alpine.data('claudeConfig', window.Components.claudeConfig);
|
|
15
|
+
Alpine.data('logsViewer', window.Components.logsViewer);
|
|
16
|
+
Alpine.data('addAccountModal', window.Components.addAccountModal);
|
|
17
|
+
|
|
18
|
+
// View Loader Directive
|
|
19
|
+
Alpine.directive('load-view', (el, { expression }, { evaluate }) => {
|
|
20
|
+
if (!window.viewCache) window.viewCache = new Map();
|
|
21
|
+
|
|
22
|
+
// Evaluate the expression to get the actual view name (removes quotes)
|
|
23
|
+
const viewName = evaluate(expression);
|
|
24
|
+
|
|
25
|
+
if (window.viewCache.has(viewName)) {
|
|
26
|
+
el.innerHTML = window.viewCache.get(viewName);
|
|
27
|
+
Alpine.initTree(el);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fetch(`views/${viewName}.html?t=${Date.now()}`)
|
|
32
|
+
.then(response => {
|
|
33
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
34
|
+
return response.text();
|
|
35
|
+
})
|
|
36
|
+
.then(html => {
|
|
37
|
+
// Update cache (optional, or remove if we want always-fresh)
|
|
38
|
+
// keeping cache for session performance, but initial load will now bypass browser cache
|
|
39
|
+
window.viewCache.set(viewName, html);
|
|
40
|
+
el.innerHTML = html;
|
|
41
|
+
Alpine.initTree(el);
|
|
42
|
+
})
|
|
43
|
+
.catch(err => {
|
|
44
|
+
console.error('Failed to load view:', viewName, err);
|
|
45
|
+
el.innerHTML = `<div class="p-4 border border-red-500/50 bg-red-500/10 rounded-lg text-red-400 font-mono text-sm">
|
|
46
|
+
Error loading view: ${viewName}<br>
|
|
47
|
+
<span class="text-xs opacity-75">${err.message}</span>
|
|
48
|
+
</div>`;
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Main App Controller
|
|
53
|
+
Alpine.data('app', () => ({
|
|
54
|
+
get connectionStatus() {
|
|
55
|
+
return Alpine.store('data')?.connectionStatus || 'connecting';
|
|
56
|
+
},
|
|
57
|
+
get loading() {
|
|
58
|
+
return Alpine.store('data')?.loading || false;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
sidebarOpen: window.innerWidth >= 1024,
|
|
62
|
+
toggleSidebar() {
|
|
63
|
+
this.sidebarOpen = !this.sidebarOpen;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
init() {
|
|
67
|
+
console.log('App controller initialized');
|
|
68
|
+
|
|
69
|
+
// Handle responsive sidebar transitions
|
|
70
|
+
let lastWidth = window.innerWidth;
|
|
71
|
+
let resizeTimeout = null;
|
|
72
|
+
|
|
73
|
+
window.addEventListener('resize', () => {
|
|
74
|
+
if (resizeTimeout) clearTimeout(resizeTimeout);
|
|
75
|
+
|
|
76
|
+
resizeTimeout = setTimeout(() => {
|
|
77
|
+
const currentWidth = window.innerWidth;
|
|
78
|
+
const lgBreakpoint = 1024;
|
|
79
|
+
|
|
80
|
+
// Desktop -> Mobile: Auto-close sidebar to prevent overlay blocking screen
|
|
81
|
+
if (lastWidth >= lgBreakpoint && currentWidth < lgBreakpoint) {
|
|
82
|
+
this.sidebarOpen = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Mobile -> Desktop: Auto-open sidebar (restore standard desktop layout)
|
|
86
|
+
if (lastWidth < lgBreakpoint && currentWidth >= lgBreakpoint) {
|
|
87
|
+
this.sidebarOpen = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
lastWidth = currentWidth;
|
|
91
|
+
}, 150);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Theme setup
|
|
95
|
+
document.documentElement.setAttribute('data-theme', 'black');
|
|
96
|
+
document.documentElement.classList.add('dark');
|
|
97
|
+
|
|
98
|
+
// Chart Defaults
|
|
99
|
+
if (typeof Chart !== 'undefined') {
|
|
100
|
+
Chart.defaults.color = window.utils.getThemeColor('--color-text-dim');
|
|
101
|
+
Chart.defaults.borderColor = window.utils.getThemeColor('--color-space-border');
|
|
102
|
+
Chart.defaults.font.family = '"JetBrains Mono", monospace';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Start Data Polling
|
|
106
|
+
this.startAutoRefresh();
|
|
107
|
+
document.addEventListener('refresh-interval-changed', () => this.startAutoRefresh());
|
|
108
|
+
|
|
109
|
+
// Initial Fetch
|
|
110
|
+
Alpine.store('data').fetchData();
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
refreshTimer: null,
|
|
114
|
+
|
|
115
|
+
fetchData() {
|
|
116
|
+
Alpine.store('data').fetchData();
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
startAutoRefresh() {
|
|
120
|
+
if (this.refreshTimer) clearInterval(this.refreshTimer);
|
|
121
|
+
const interval = parseInt(Alpine.store('settings')?.refreshInterval || 60);
|
|
122
|
+
if (interval > 0) {
|
|
123
|
+
this.refreshTimer = setInterval(() => Alpine.store('data').fetchData(), interval * 1000);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
t(key) {
|
|
128
|
+
return Alpine.store('global')?.t(key) || key;
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
async addAccountWeb(reAuthEmail = null) {
|
|
132
|
+
const password = Alpine.store('global').webuiPassword;
|
|
133
|
+
try {
|
|
134
|
+
const urlPath = reAuthEmail
|
|
135
|
+
? `/api/auth/url?email=${encodeURIComponent(reAuthEmail)}`
|
|
136
|
+
: '/api/auth/url';
|
|
137
|
+
|
|
138
|
+
const { response, newPassword } = await window.utils.request(urlPath, {}, password);
|
|
139
|
+
if (newPassword) Alpine.store('global').webuiPassword = newPassword;
|
|
140
|
+
|
|
141
|
+
const data = await response.json();
|
|
142
|
+
|
|
143
|
+
if (data.status === 'ok') {
|
|
144
|
+
// Show info toast that OAuth is in progress
|
|
145
|
+
Alpine.store('global').showToast(Alpine.store('global').t('oauthInProgress'), 'info');
|
|
146
|
+
|
|
147
|
+
// Open OAuth window
|
|
148
|
+
const oauthWindow = window.open(data.url, 'google_oauth', 'width=600,height=700,scrollbars=yes');
|
|
149
|
+
|
|
150
|
+
// Poll for account changes instead of relying on postMessage
|
|
151
|
+
// (since OAuth callback is now on port 51121, not this server)
|
|
152
|
+
const initialAccountCount = Alpine.store('data').accounts.length;
|
|
153
|
+
let pollCount = 0;
|
|
154
|
+
const maxPolls = 60; // 2 minutes (2 second intervals)
|
|
155
|
+
let cancelled = false;
|
|
156
|
+
|
|
157
|
+
// Show progress modal
|
|
158
|
+
Alpine.store('global').oauthProgress = {
|
|
159
|
+
active: true,
|
|
160
|
+
current: 0,
|
|
161
|
+
max: maxPolls,
|
|
162
|
+
cancel: () => {
|
|
163
|
+
cancelled = true;
|
|
164
|
+
clearInterval(pollInterval);
|
|
165
|
+
Alpine.store('global').oauthProgress.active = false;
|
|
166
|
+
Alpine.store('global').showToast(Alpine.store('global').t('oauthCancelled'), 'info');
|
|
167
|
+
if (oauthWindow && !oauthWindow.closed) {
|
|
168
|
+
oauthWindow.close();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const pollInterval = setInterval(async () => {
|
|
174
|
+
if (cancelled) {
|
|
175
|
+
clearInterval(pollInterval);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
pollCount++;
|
|
180
|
+
Alpine.store('global').oauthProgress.current = pollCount;
|
|
181
|
+
|
|
182
|
+
// Check if OAuth window was closed manually
|
|
183
|
+
if (oauthWindow && oauthWindow.closed && !cancelled) {
|
|
184
|
+
clearInterval(pollInterval);
|
|
185
|
+
Alpine.store('global').oauthProgress.active = false;
|
|
186
|
+
Alpine.store('global').showToast(Alpine.store('global').t('oauthWindowClosed'), 'warning');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Refresh account list
|
|
191
|
+
await Alpine.store('data').fetchData();
|
|
192
|
+
|
|
193
|
+
// Check if new account was added
|
|
194
|
+
const currentAccountCount = Alpine.store('data').accounts.length;
|
|
195
|
+
if (currentAccountCount > initialAccountCount) {
|
|
196
|
+
clearInterval(pollInterval);
|
|
197
|
+
Alpine.store('global').oauthProgress.active = false;
|
|
198
|
+
|
|
199
|
+
const actionKey = reAuthEmail ? 'accountReauthSuccess' : 'accountAddedSuccess';
|
|
200
|
+
Alpine.store('global').showToast(
|
|
201
|
+
Alpine.store('global').t(actionKey),
|
|
202
|
+
'success'
|
|
203
|
+
);
|
|
204
|
+
document.getElementById('add_account_modal')?.close();
|
|
205
|
+
|
|
206
|
+
if (oauthWindow && !oauthWindow.closed) {
|
|
207
|
+
oauthWindow.close();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Stop polling after max attempts
|
|
212
|
+
if (pollCount >= maxPolls) {
|
|
213
|
+
clearInterval(pollInterval);
|
|
214
|
+
Alpine.store('global').oauthProgress.active = false;
|
|
215
|
+
Alpine.store('global').showToast(
|
|
216
|
+
Alpine.store('global').t('oauthTimeout'),
|
|
217
|
+
'warning'
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}, 2000); // Poll every 2 seconds
|
|
221
|
+
} else {
|
|
222
|
+
Alpine.store('global').showToast(data.error || Alpine.store('global').t('failedToGetAuthUrl'), 'error');
|
|
223
|
+
}
|
|
224
|
+
} catch (e) {
|
|
225
|
+
Alpine.store('global').showToast(Alpine.store('global').t('failedToStartOAuth') + ': ' + e.message, 'error');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}));
|
|
229
|
+
});
|