diff --git a/js/accounts/auth.js b/js/accounts/auth.js index 4f22669..6dab33a 100644 --- a/js/accounts/auth.js +++ b/js/accounts/auth.js @@ -1,39 +1,35 @@ // js/accounts/auth.js -import { auth, provider } from './config.js'; -import { - signInWithPopup, - signInWithRedirect, - getRedirectResult, - signOut as firebaseSignOut, - onAuthStateChanged, - signInWithEmailAndPassword, - createUserWithEmailAndPassword, - sendPasswordResetEmail, -} from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js'; +import { auth } from './config.js'; export class AuthManager { constructor() { this.user = null; - this.unsubscribe = null; this.authListeners = []; this.init(); } - init() { - if (!auth) return; + async init() { + const params = new URLSearchParams(window.location.search); + const userId = params.get('userId'); + const secret = params.get('secret'); - this.unsubscribe = onAuthStateChanged(auth, (user) => { - this.user = user; - this.updateUI(user); + if (userId && secret) { + try { + await auth.createSession(userId, secret); + window.history.replaceState({}, '', window.location.pathname); + } catch (error) { + console.error('OAuth session creation failed:', error); + } + } - this.authListeners.forEach((listener) => listener(user)); - }); - - // Handle redirect result (for Linux/Mobile where popup might be blocked) - getRedirectResult(auth).catch((error) => { - console.error('Redirect Login failed:', error); - alert(`Login failed: ${error.message}`); - }); + try { + this.user = await auth.get(); + this.updateUI(this.user); + this.authListeners.forEach((listener) => listener(this.user)); + } catch (error) { + this.user = null; + this.updateUI(null); + } } onAuthStateChanged(callback) { @@ -45,55 +41,25 @@ export class AuthManager { } async signInWithGoogle() { - if (!auth) { - alert('Firebase is not configured. Please check console.'); - return; - } - try { - const result = await signInWithPopup(auth, provider); - - if (result.user) { - console.log('Login successful:', result.user.email); - this.user = result.user; - this.updateUI(result.user); - this.authListeners.forEach((listener) => listener(result.user)); - return result.user; - } + auth.createOAuth2Session( + 'google', + window.location.origin + '/index.html', + window.location.origin + '/login.html' + ); } catch (error) { console.error('Login failed:', error); - - // On Linux, if popup is blocked or fails, we might be forced to redirect, - // but we've seen it "bug the app", so we alert the user first. - if (error.code === 'auth/popup-blocked' || error.code === 'auth/cancelled-popup-request') { - if ( - confirm( - 'The login popup was blocked or failed to communicate. Would you like to try a redirect instead? Note: This may reload the application.' - ) - ) { - try { - await signInWithRedirect(auth, provider); - return; - } catch (redirectError) { - console.error('Redirect fallback failed:', redirectError); - alert(`Login failed: ${redirectError.message}`); - } - } - } else { - alert(`Login failed: ${error.message}`); - } - throw error; + alert(`Login failed: ${error.message}`); } } async signInWithEmail(email, password) { - if (!auth) { - alert('Firebase is not configured.'); - return; - } try { - const result = await signInWithEmailAndPassword(auth, email, password); - return result.user; + await auth.createEmailPasswordSession(email, password); + this.user = await auth.get(); + this.updateUI(this.user); + this.authListeners.forEach((listener) => listener(this.user)); + return this.user; } catch (error) { console.error('Email Login failed:', error); alert(`Login failed: ${error.message}`); @@ -102,13 +68,13 @@ export class AuthManager { } async signUpWithEmail(email, password) { - if (!auth) { - alert('Firebase is not configured.'); - return; - } try { - const result = await createUserWithEmailAndPassword(auth, email, password); - return result.user; + await auth.create('unique()', email, password); + await auth.createEmailPasswordSession(email, password); + this.user = await auth.get(); + this.updateUI(this.user); + this.authListeners.forEach((listener) => listener(this.user)); + return this.user; } catch (error) { console.error('Sign Up failed:', error); alert(`Sign Up failed: ${error.message}`); @@ -117,12 +83,11 @@ export class AuthManager { } async sendPasswordReset(email) { - if (!auth) { - alert('Firebase is not configured.'); - return; - } try { - await sendPasswordResetEmail(auth, email); + await auth.createRecovery( + email, + window.location.origin + '/reset-password.html' + ); alert(`Password reset email sent to ${email}`); } catch (error) { console.error('Password reset failed:', error); @@ -132,17 +97,16 @@ export class AuthManager { } async signOut() { - if (!auth) return; - try { - await firebaseSignOut(auth); + await auth.deleteSession('current'); + this.user = null; + this.updateUI(null); + this.authListeners.forEach((listener) => listener(null)); + if (window.__AUTH_GATE__) { - try { - await fetch('/api/auth/logout', { method: 'POST' }); - } catch { - // Server endpoint may not exist in dev mode - } window.location.href = '/login'; + } else { + window.location.reload(); } } catch (error) { console.error('Logout failed:', error); @@ -151,9 +115,9 @@ export class AuthManager { } updateUI(user) { - const connectBtn = document.getElementById('firebase-connect-btn'); - const clearDataBtn = document.getElementById('firebase-clear-cloud-btn'); - const statusText = document.getElementById('firebase-status'); + const connectBtn = document.getElementById('firebase-connect-btn'); + const clearDataBtn = document.getElementById('firebase-clear-cloud-btn'); + const statusText = document.getElementById('firebase-status'); const emailContainer = document.getElementById('email-auth-container'); const emailToggleBtn = document.getElementById('toggle-email-auth-btn'); @@ -164,17 +128,15 @@ export class AuthManager { connectBtn.textContent = 'Sign Out'; connectBtn.classList.add('danger'); connectBtn.onclick = () => this.signOut(); - if (clearDataBtn) clearDataBtn.style.display = 'none'; + if (clearDataBtn) clearDataBtn.style.display = 'none'; if (emailContainer) emailContainer.style.display = 'none'; if (emailToggleBtn) emailToggleBtn.style.display = 'none'; - if (statusText) statusText.textContent = user ? `Signed in as ${user.email}` : 'Signed in'; + if (statusText) statusText.textContent = user ? `Signed in as ${user.email}` : 'Signed in'; - // Account page: clean up unnecessary text const accountPage = document.getElementById('page-account'); if (accountPage) { const title = accountPage.querySelector('.section-title'); if (title) title.textContent = 'Account'; - // Hide description + privacy paragraphs, keep only status accountPage.querySelectorAll('.account-content > p, .account-content > div').forEach((el) => { if (el.id !== 'firebase-status' && el.id !== 'auth-buttons-container') { el.style.display = 'none'; @@ -182,12 +144,10 @@ export class AuthManager { }); } - // Settings page: hide custom DB/Auth config when fully server-configured const customDbBtn = document.getElementById('custom-db-btn'); if (customDbBtn) { - const fbFromEnv = !!window.__FIREBASE_CONFIG__; const pbFromEnv = !!window.__POCKETBASE_URL__; - if (fbFromEnv && pbFromEnv) { + if (pbFromEnv) { const settingItem = customDbBtn.closest('.setting-item'); if (settingItem) settingItem.style.display = 'none'; } @@ -201,22 +161,20 @@ export class AuthManager { connectBtn.classList.add('danger'); connectBtn.onclick = () => this.signOut(); - if (clearDataBtn) clearDataBtn.style.display = 'block'; + if (clearDataBtn) clearDataBtn.style.display = 'block'; if (emailContainer) emailContainer.style.display = 'none'; if (emailToggleBtn) emailToggleBtn.style.display = 'none'; - - if (statusText) statusText.textContent = `Signed in as ${user.email}`; + if (statusText) statusText.textContent = `Signed in as ${user.email}`; } else { connectBtn.textContent = 'Connect with Google'; connectBtn.classList.remove('danger'); connectBtn.onclick = () => this.signInWithGoogle(); - if (clearDataBtn) clearDataBtn.style.display = 'none'; + if (clearDataBtn) clearDataBtn.style.display = 'none'; if (emailToggleBtn) emailToggleBtn.style.display = 'inline-block'; - - if (statusText) statusText.textContent = 'Sync your library across devices'; + if (statusText) statusText.textContent = 'Sync your library across devices'; } } } -export const authManager = new AuthManager(); +export const authManager = new AuthManager(); \ No newline at end of file diff --git a/js/accounts/config.js b/js/accounts/config.js index a7956da..1a3ebf6 100644 --- a/js/accounts/config.js +++ b/js/accounts/config.js @@ -1,243 +1,12 @@ -//js/accounts/config.js -import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js'; -import { getAuth, GoogleAuthProvider } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js'; -import { getDatabase } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-database.js'; +// js/accounts/config.js +import { Client, Account } from 'appwrite'; -let app = null; -let auth = null; -let database = null; -let provider = null; +const client = new Client() + .setEndpoint('https://auth.samidy.xyz/v1') + .setProject('auth-for-monochrome'); -const STORAGE_KEY = 'monochrome-firebase-config'; +const account = new Account(client); -const DEFAULT_CONFIG = { - apiKey: 'AIzaSyDPU-unAjuLtQJt4IkGS5faG50UCF7lYyA', - authDomain: 'monochrome-database.firebaseapp.com', - projectId: 'monochrome-database', - storageBucket: 'monochrome-database.firebasestorage.app', - messagingSenderId: '895657412760', - appId: '1:895657412760:web:e81c5044c7f4e9b799e8ed', -}; - -function getStoredConfig() { - try { - const stored = localStorage.getItem(STORAGE_KEY); - return stored ? JSON.parse(stored) : null; - } catch (e) { - console.warn('Failed to parse Firebase config from storage', e); - return null; - } -} - -// Attempt to initialize on load -// Priority: server-injected env (auth gate) > localStorage > default -const storedConfig = getStoredConfig(); -const config = window.__FIREBASE_CONFIG__ || storedConfig || DEFAULT_CONFIG; - -if (config) { - try { - app = initializeApp(config); - auth = getAuth(app); - database = getDatabase(app); - provider = new GoogleAuthProvider(); - console.log('Firebase initialized from ' + (storedConfig ? 'saved' : 'default') + ' config'); - } catch (error) { - console.error('Error initializing Firebase:', error); - } -} else { - console.log('No Firebase config found.'); -} - -export function saveFirebaseConfig(configObj) { - if (!configObj) return; - localStorage.setItem(STORAGE_KEY, JSON.stringify(configObj)); -} - -export function clearFirebaseConfig() { - localStorage.removeItem(STORAGE_KEY); -} - -/** - * Generates a shareable URL containing the encoded configuration. - * @param {Object} config - The Firebase configuration object. - * @returns {string} The full URL with the config hash. - */ -export function generateShareLink(config) { - if (!config) return null; - try { - const json = JSON.stringify(config); - // Base64 encode (safe for URL hash) - const encoded = btoa(json); - const url = new URL(window.location.href); - url.hash = `#setup_firebase=${encoded}`; - return url.toString(); - } catch (e) { - console.error('Failed to generate share link:', e); - return null; - } -} - -/** - * Checks the current URL for a shared configuration. - * If found, prompts the user to import it. - * @returns {boolean} True if a config was handled/processed. - */ -export function checkAndImportConfig() { - const hash = window.location.hash; - if (!hash.startsWith('#setup_firebase=')) return false; - - const encoded = hash.split('#setup_firebase=')[1]; - if (!encoded) return false; - - try { - const json = atob(encoded); - const config = JSON.parse(json); - - // Validate basic structure - if (!config.apiKey || !config.authDomain) { - alert('The shared configuration link appears to be invalid.'); - return false; - } - - if (confirm('A Firebase configuration was detected in the link. Do you want to import it and enable Sync?')) { - saveFirebaseConfig(config); - // Clean URL - window.history.replaceState(null, null, window.location.pathname); - alert('Configuration imported successfully! The app will now reload.'); - window.location.reload(); - return true; - } else { - // User rejected, clean URL anyway to avoid re-prompting - window.history.replaceState(null, null, window.location.pathname + '#settings'); - } - } catch (e) { - console.error('Failed to parse shared config:', e); - alert('Failed to read configuration from link. The link might be corrupted.'); - } - return false; -} - -export function initializeFirebaseSettingsUI() { - // Check for shared config in URL first - checkAndImportConfig(); - - const firebaseConfigInput = document.getElementById('firebase-config-input'); - const saveFirebaseConfigBtn = document.getElementById('save-firebase-config-btn'); - const clearFirebaseConfigBtn = document.getElementById('clear-firebase-config-btn'); - const shareFirebaseConfigBtn = document.getElementById('share-firebase-config-btn'); - const toggleFirebaseConfigBtn = document.getElementById('toggle-firebase-config-btn'); - const customFirebaseConfigContainer = document.getElementById('custom-firebase-config-container'); - - // Toggle Button Logic - if (toggleFirebaseConfigBtn && customFirebaseConfigContainer) { - toggleFirebaseConfigBtn.addEventListener('click', () => { - const isVisible = customFirebaseConfigContainer.classList.contains('visible'); - if (isVisible) { - customFirebaseConfigContainer.classList.remove('visible'); - toggleFirebaseConfigBtn.textContent = 'Custom Configuration'; - } else { - customFirebaseConfigContainer.classList.add('visible'); - toggleFirebaseConfigBtn.textContent = 'Hide Custom Configuration'; - } - }); - } - - // Populate current config - if (firebaseConfigInput) { - const currentConfig = localStorage.getItem(STORAGE_KEY); - if (currentConfig) { - try { - firebaseConfigInput.value = JSON.stringify(JSON.parse(currentConfig), null, 2); - // If custom config exists, show the container - if (customFirebaseConfigContainer && toggleFirebaseConfigBtn) { - customFirebaseConfigContainer.classList.add('visible'); - toggleFirebaseConfigBtn.textContent = 'Hide Custom Configuration'; - } - } catch { - firebaseConfigInput.value = currentConfig; - } - } - } - - // Share Button - if (shareFirebaseConfigBtn) { - shareFirebaseConfigBtn.addEventListener('click', () => { - const currentConfigStr = localStorage.getItem(STORAGE_KEY); - if (!currentConfigStr) { - alert('No configuration saved to share.'); - return; - } - try { - const config = JSON.parse(currentConfigStr); - const link = generateShareLink(config); - if (link) { - navigator.clipboard - .writeText(link) - .then(() => { - alert('Magic Link copied to clipboard! Send it to your other device.'); - }) - .catch((err) => { - console.error('Clipboard error:', err); - prompt('Copy this link:', link); - }); - } - } catch { - alert('Invalid configuration found.'); - } - }); - } - - // Save Button - if (saveFirebaseConfigBtn) { - saveFirebaseConfigBtn.addEventListener('click', () => { - const inputVal = firebaseConfigInput.value.trim(); - if (!inputVal) { - alert('Please enter a valid configuration.'); - return; - } - - try { - let cleaned = inputVal; - // Remove variable declaration if present (e.g., "const firebaseConfig = ") - if (cleaned.includes('=')) { - cleaned = cleaned.substring(cleaned.indexOf('=') + 1); - } - // Remove trailing semicolon - cleaned = cleaned.trim(); - if (cleaned.endsWith(';')) { - cleaned = cleaned.slice(0, -1); - } - - // Convert JS Object format to JSON format - const jsonReady = cleaned - .replace(/([{,]\s*)([a-zA-Z0-9_]+)\s*:/g, '$1"$2":') // Wrap keys in double quotes - .replace(/:\s*'([^']*)'/g, ': "$1"') // Replace single-quoted values with double quotes - .replace(/,\s*([}\]])/g, '$1'); // Remove trailing commas - - const config = JSON.parse(jsonReady); - saveFirebaseConfig(config); - alert('Configuration saved. Reloading...'); - window.location.reload(); - } catch (error) { - console.error('Invalid Config:', error); - alert('Could not parse configuration. Please ensure it looks like a valid JSON or JS object.'); - } - }); - } - - // Clear Button - if (clearFirebaseConfigBtn) { - clearFirebaseConfigBtn.addEventListener('click', () => { - if ( - confirm( - 'Are you sure you want to remove the custom configuration? The app will revert to the shared default database.' - ) - ) { - clearFirebaseConfig(); - window.location.reload(); - } - }); - } -} - -export { app, auth, database, provider }; +export { client, account as auth }; +export const saveFirebaseConfig = () => { console.log("ill fix this tomorrow"); }; +export const clearFirebaseConfig = () => { console.log("ill fix this tomorrow"); }; \ No newline at end of file diff --git a/js/accounts/pocketbase.js b/js/accounts/pocketbase.js index f52a4e0..048ea6c 100644 --- a/js/accounts/pocketbase.js +++ b/js/accounts/pocketbase.js @@ -4,7 +4,7 @@ import { db } from '../db.js'; import { authManager } from './auth.js'; const PUBLIC_COLLECTION = 'public_playlists'; -const DEFAULT_POCKETBASE_URL = 'https://monodb.samidy.com'; +const DEFAULT_POCKETBASE_URL = 'https://data.samidy.xyz'; const POCKETBASE_URL = localStorage.getItem('monochrome-pocketbase-url') || DEFAULT_POCKETBASE_URL; console.log('[PocketBase] Using URL:', POCKETBASE_URL); @@ -57,7 +57,7 @@ const syncManager = { const user = authManager.user; if (!user) return null; - const record = await this._getUserRecord(user.uid); + const record = await this._getUserRecord(user.$id); if (!record) return null; const library = this.safeParseInternal(record.library, 'library', {}); @@ -143,7 +143,7 @@ const syncManager = { const user = authManager.user; if (!user) return; - const record = await this._getUserRecord(user.uid); + const record = await this._getUserRecord(user.$id); if (!record) return; let library = this.safeParseInternal(record.library, 'library', {}); @@ -161,7 +161,7 @@ const syncManager = { delete library[pluralType][key]; } - await this._updateUserJSON(user.uid, 'library', library); + await this._updateUserJSON(user.$id, 'library', library); }, _minifyItem(type, item) { @@ -254,20 +254,20 @@ const syncManager = { const user = authManager.user; if (!user) return; - const record = await this._getUserRecord(user.uid); + const record = await this._getUserRecord(user.$id); if (!record) return; let history = this.safeParseInternal(record.history, 'history', []); const newHistory = [historyEntry, ...history].slice(0, 100); - await this._updateUserJSON(user.uid, 'history', newHistory); + await this._updateUserJSON(user.$id, 'history', newHistory); }, async syncUserPlaylist(playlist, action) { const user = authManager.user; if (!user) return; - const record = await this._getUserRecord(user.uid); + const record = await this._getUserRecord(user.$id); if (!record) return; let userPlaylists = this.safeParseInternal(record.user_playlists, 'user_playlists', {}); @@ -293,14 +293,14 @@ const syncManager = { } } - await this._updateUserJSON(user.uid, 'user_playlists', userPlaylists); + await this._updateUserJSON(user.$id, 'user_playlists', userPlaylists); }, async syncUserFolder(folder, action) { const user = authManager.user; if (!user) return; - const record = await this._getUserRecord(user.uid); + const record = await this._getUserRecord(user.$id); if (!record) return; let userFolders = this.safeParseInternal(record.user_folders, 'user_folders', {}); @@ -318,7 +318,7 @@ const syncManager = { }; } - await this._updateUserJSON(user.uid, 'user_folders', userFolders); + await this._updateUserJSON(user.$id, 'user_folders', userFolders); }, async getPublicPlaylist(uuid) { @@ -391,7 +391,7 @@ const syncManager = { async publishPlaylist(playlist) { if (!playlist || !playlist.id) return; - const uid = authManager.user?.uid; + const uid = authManager.user?.$id; if (!uid) return; const data = { @@ -431,7 +431,7 @@ const syncManager = { }, async unpublishPlaylist(uuid) { - const uid = authManager.user?.uid; + const uid = authManager.user?.$id; if (!uid) return; try { @@ -467,7 +467,7 @@ const syncManager = { async updateProfile(data) { const user = authManager.user; if (!user) return; - const record = await this._getUserRecord(user.uid); + const record = await this._getUserRecord(user.$id); if (!record) return; const updateData = { ...data }; @@ -475,7 +475,7 @@ const syncManager = { updateData.privacy = JSON.stringify(updateData.privacy); } - await this.pb.collection('DB_users').update(record.id, updateData, { f_id: user.uid }); + await this.pb.collection('DB_users').update(record.id, updateData, { f_id: user.$id }); if (this._userRecordCache) { this._userRecordCache = { ...this._userRecordCache, ...updateData }; } @@ -495,9 +495,9 @@ const syncManager = { if (!user) return; try { - const record = await this._getUserRecord(user.uid); + const record = await this._getUserRecord(user.$id); if (record) { - await this.pb.collection('DB_users').delete(record.id, { f_id: user.uid }); + await this.pb.collection('DB_users').delete(record.id, { f_id: user.$id }); this._userRecordCache = null; alert('Cloud data cleared successfully.'); } @@ -606,10 +606,10 @@ const syncManager = { } if (needsUpdate) { - await this._updateUserJSON(user.uid, 'library', library); - await this._updateUserJSON(user.uid, 'user_playlists', userPlaylists); - await this._updateUserJSON(user.uid, 'user_folders', userFolders); - await this._updateUserJSON(user.uid, 'history', history); + await this._updateUserJSON(user.$id, 'library', library); + await this._updateUserJSON(user.$id, 'user_playlists', userPlaylists); + await this._updateUserJSON(user.$id, 'user_folders', userFolders); + await this._updateUserJSON(user.$id, 'history', history); } const convertedData = { diff --git a/js/app.js b/js/app.js index c9f0b9f..9d65754 100644 --- a/js/app.js +++ b/js/app.js @@ -2609,7 +2609,7 @@ document.addEventListener('DOMContentLoaded', async () => { const headerAccountIcon = document.getElementById('header-account-icon'); // Temporarily disable accounts - show popup - const isAccountsDisabled = true; + const isAccountsDisabled = false; if (headerAccountBtn && headerAccountDropdown) { if (isAccountsDisabled) { diff --git a/package-lock.json b/package-lock.json index 84aff55..b7c4d6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2", "@neutralinojs/lib": "^6.5.0", + "appwrite": "^23.0.0", "butterchurn": "^2.6.7", "butterchurn-presets": "^2.4.7", "cookie-session": "^2.1.1", @@ -23,7 +24,7 @@ "@neutralinojs/neu": "^11.7.0", "eslint": "^9.39.3", "eslint-config-prettier": "^10.1.8", - "globals": "^17.3.0", + "globals": "^17.4.0", "htmlhint": "^1.9.1", "prettier": "^3.8.1", "stylelint": "^16.26.1", @@ -3149,6 +3150,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/appwrite": { + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/appwrite/-/appwrite-23.0.0.tgz", + "integrity": "sha512-K11a597npl3jsnxWKzjw163n4GguH4+/zBCOiU15yc1u+7QF0nP9mxsY4JxKrBU6bmQRtgtMTPv/6YOLSwp/QQ==", + "license": "BSD-3-Clause", + "dependencies": { + "json-bigint": "1.0.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3351,6 +3361,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -5923,6 +5942,15 @@ "node": ">=6" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -11168,6 +11196,14 @@ "color-convert": "^2.0.1" } }, + "appwrite": { + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/appwrite/-/appwrite-23.0.0.tgz", + "integrity": "sha512-K11a597npl3jsnxWKzjw163n4GguH4+/zBCOiU15yc1u+7QF0nP9mxsY4JxKrBU6bmQRtgtMTPv/6YOLSwp/QQ==", + "requires": { + "json-bigint": "1.0.0" + } + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -11313,6 +11349,11 @@ "bcp-47-match": "^2.0.0" } }, + "bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==" + }, "brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -13143,6 +13184,14 @@ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", diff --git a/package.json b/package.json index 6e111fc..4cfc7b0 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2", "@neutralinojs/lib": "^6.5.0", + "appwrite": "^23.0.0", "butterchurn": "^2.6.7", "butterchurn-presets": "^2.4.7", "cookie-session": "^2.1.1",