import Realm from 'realm'; import RNRealmPath from 'react-native-realm-path'; // import { AsyncStorage } from 'react-native'; // Realm.clearTestState(); // AsyncStorage.clear(); const userSchema = { name: 'user', primaryKey: 'id', properties: { id: 'string', token: { type: 'string', optional: true }, username: { type: 'string', optional: true }, name: { type: 'string', optional: true }, language: { type: 'string', optional: true }, status: { type: 'string', optional: true }, roles: { type: 'string[]', optional: true } } }; const serversSchema = { name: 'servers', primaryKey: 'id', properties: { id: 'string', name: { type: 'string', optional: true }, iconURL: { type: 'string', optional: true }, useRealName: { type: 'bool', optional: true }, FileUpload_MediaTypeWhiteList: { type: 'string', optional: true }, FileUpload_MaxFileSize: { type: 'int', optional: true }, roomsUpdatedAt: { type: 'date', optional: true }, version: 'string?' } }; const settingsSchema = { name: 'settings', primaryKey: '_id', properties: { _id: 'string', valueAsString: { type: 'string', optional: true }, valueAsBoolean: { type: 'bool', optional: true }, valueAsNumber: { type: 'int', optional: true }, _updatedAt: { type: 'date', optional: true } } }; const permissionsSchema = { name: 'permissions', primaryKey: '_id', properties: { _id: 'string', roles: 'string[]', _updatedAt: { type: 'date', optional: true } } }; const roomsSchema = { name: 'rooms', primaryKey: '_id', properties: { _id: 'string', name: 'string?', broadcast: { type: 'bool', optional: true } } }; const subscriptionSchema = { name: 'subscriptions', primaryKey: '_id', properties: { _id: 'string', f: { type: 'bool', optional: true }, t: 'string', ts: { type: 'date', optional: true }, ls: { type: 'date', optional: true }, name: { type: 'string', indexed: true }, fname: { type: 'string', optional: true }, rid: { type: 'string', indexed: true }, open: { type: 'bool', optional: true }, alert: { type: 'bool', optional: true }, roles: 'string[]', unread: { type: 'int', optional: true }, userMentions: { type: 'int', optional: true }, roomUpdatedAt: { type: 'date', optional: true }, ro: { type: 'bool', optional: true }, lastOpen: { type: 'date', optional: true }, lastMessage: { type: 'messages', optional: true }, description: { type: 'string', optional: true }, announcement: { type: 'string', optional: true }, topic: { type: 'string', optional: true }, blocked: { type: 'bool', optional: true }, blocker: { type: 'bool', optional: true }, reactWhenReadOnly: { type: 'bool', optional: true }, archived: { type: 'bool', optional: true }, joinCodeRequired: { type: 'bool', optional: true }, notifications: { type: 'bool', optional: true }, muted: 'string[]', broadcast: { type: 'bool', optional: true }, prid: { type: 'string', optional: true }, draftMessage: { type: 'string', optional: true }, lastThreadSync: 'date?', autoTranslate: 'bool?', autoTranslateLanguage: 'string?' } }; const usersSchema = { name: 'users', primaryKey: '_id', properties: { _id: 'string', username: 'string', name: { type: 'string', optional: true } } }; const attachmentFields = { name: 'attachmentFields', properties: { title: { type: 'string', optional: true }, value: { type: 'string', optional: true }, short: { type: 'bool', optional: true } } }; const attachment = { name: 'attachment', properties: { description: { type: 'string', optional: true }, image_size: { type: 'int', optional: true }, image_type: { type: 'string', optional: true }, image_url: { type: 'string', optional: true }, audio_size: { type: 'int', optional: true }, audio_type: { type: 'string', optional: true }, audio_url: { type: 'string', optional: true }, video_size: { type: 'int', optional: true }, video_type: { type: 'string', optional: true }, video_url: { type: 'string', optional: true }, title: { type: 'string', optional: true }, title_link: { type: 'string', optional: true }, // title_link_download: { type: 'bool', optional: true }, type: { type: 'string', optional: true }, author_icon: { type: 'string', optional: true }, author_name: { type: 'string', optional: true }, author_link: { type: 'string', optional: true }, text: { type: 'string', optional: true }, color: { type: 'string', optional: true }, ts: { type: 'date', optional: true }, attachments: { type: 'list', objectType: 'attachment' }, fields: { type: 'list', objectType: 'attachmentFields', default: [] } } }; const url = { name: 'url', primaryKey: 'url', properties: { // _id: { type: 'int', optional: true }, url: { type: 'string', optional: true }, title: { type: 'string', optional: true }, description: { type: 'string', optional: true }, image: { type: 'string', optional: true } } }; const messagesReactionsSchema = { name: 'messagesReactions', primaryKey: '_id', properties: { _id: 'string', emoji: 'string', usernames: 'string[]' } }; const messagesTranslationsSchema = { name: 'messagesTranslations', primaryKey: '_id', properties: { _id: 'string', language: 'string', value: 'string' } }; const messagesEditedBySchema = { name: 'messagesEditedBy', primaryKey: '_id', properties: { _id: { type: 'string', optional: true }, username: { type: 'string', optional: true } } }; const messagesSchema = { name: 'messages', primaryKey: '_id', properties: { _id: 'string', msg: { type: 'string', optional: true }, t: { type: 'string', optional: true }, rid: { type: 'string', indexed: true }, ts: 'date', u: 'users', alias: { type: 'string', optional: true }, parseUrls: { type: 'bool', optional: true }, groupable: { type: 'bool', optional: true }, avatar: { type: 'string', optional: true }, attachments: { type: 'list', objectType: 'attachment' }, urls: { type: 'list', objectType: 'url', default: [] }, _updatedAt: { type: 'date', optional: true }, status: { type: 'int', optional: true }, pinned: { type: 'bool', optional: true }, starred: { type: 'bool', optional: true }, editedBy: 'messagesEditedBy', reactions: { type: 'list', objectType: 'messagesReactions' }, role: { type: 'string', optional: true }, drid: { type: 'string', optional: true }, dcount: { type: 'int', optional: true }, dlm: { type: 'date', optional: true }, tmid: { type: 'string', optional: true }, tcount: { type: 'int', optional: true }, tlm: { type: 'date', optional: true }, replies: 'string[]', mentions: { type: 'list', objectType: 'users' }, channels: { type: 'list', objectType: 'rooms' }, unread: { type: 'bool', optional: true }, autoTranslate: { type: 'bool', default: false }, translations: { type: 'list', objectType: 'messagesTranslations' } } }; const threadsSchema = { name: 'threads', primaryKey: '_id', properties: { _id: 'string', msg: { type: 'string', optional: true }, t: { type: 'string', optional: true }, rid: { type: 'string', indexed: true }, ts: 'date', u: 'users', alias: { type: 'string', optional: true }, parseUrls: { type: 'bool', optional: true }, groupable: { type: 'bool', optional: true }, avatar: { type: 'string', optional: true }, attachments: { type: 'list', objectType: 'attachment' }, urls: { type: 'list', objectType: 'url', default: [] }, _updatedAt: { type: 'date', optional: true }, status: { type: 'int', optional: true }, pinned: { type: 'bool', optional: true }, starred: { type: 'bool', optional: true }, editedBy: 'messagesEditedBy', reactions: { type: 'list', objectType: 'messagesReactions' }, role: { type: 'string', optional: true }, drid: { type: 'string', optional: true }, dcount: { type: 'int', optional: true }, dlm: { type: 'date', optional: true }, tmid: { type: 'string', optional: true }, tcount: { type: 'int', optional: true }, tlm: { type: 'date', optional: true }, replies: 'string[]', mentions: { type: 'list', objectType: 'users' }, channels: { type: 'list', objectType: 'rooms' }, unread: { type: 'bool', optional: true }, autoTranslate: { type: 'bool', default: false }, translations: { type: 'list', objectType: 'messagesTranslations' }, draftMessage: 'string?' } }; const threadMessagesSchema = { name: 'threadMessages', primaryKey: '_id', properties: { _id: 'string', msg: { type: 'string', optional: true }, t: { type: 'string', optional: true }, rid: { type: 'string', indexed: true }, ts: 'date', u: 'users', alias: { type: 'string', optional: true }, parseUrls: { type: 'bool', optional: true }, groupable: { type: 'bool', optional: true }, avatar: { type: 'string', optional: true }, attachments: { type: 'list', objectType: 'attachment' }, urls: { type: 'list', objectType: 'url', default: [] }, _updatedAt: { type: 'date', optional: true }, status: { type: 'int', optional: true }, pinned: { type: 'bool', optional: true }, starred: { type: 'bool', optional: true }, editedBy: 'messagesEditedBy', reactions: { type: 'list', objectType: 'messagesReactions' }, role: { type: 'string', optional: true }, replies: 'string[]', mentions: { type: 'list', objectType: 'users' }, channels: { type: 'list', objectType: 'rooms' }, unread: { type: 'bool', optional: true }, autoTranslate: { type: 'bool', default: false }, translations: { type: 'list', objectType: 'messagesTranslations' } } }; const frequentlyUsedEmojiSchema = { name: 'frequentlyUsedEmoji', primaryKey: 'content', properties: { content: { type: 'string', optional: true }, extension: { type: 'string', optional: true }, isCustom: 'bool', count: 'int' } }; const slashCommandSchema = { name: 'slashCommand', primaryKey: 'command', properties: { command: 'string', params: { type: 'string', optional: true }, description: { type: 'string', optional: true }, clientOnly: { type: 'bool', optional: true }, providesPreview: { type: 'bool', optional: true } } }; const customEmojisSchema = { name: 'customEmojis', primaryKey: '_id', properties: { _id: 'string', name: 'string', aliases: 'string[]', extension: 'string', _updatedAt: { type: 'date', optional: true } } }; const rolesSchema = { name: 'roles', primaryKey: '_id', properties: { _id: 'string', description: { type: 'string', optional: true } } }; const uploadsSchema = { name: 'uploads', primaryKey: 'path', properties: { path: 'string', rid: 'string', name: { type: 'string', optional: true }, description: { type: 'string', optional: true }, size: { type: 'int', optional: true }, type: { type: 'string', optional: true }, store: { type: 'string', optional: true }, progress: { type: 'int', default: 1 }, error: { type: 'bool', default: false } } }; const usersTypingSchema = { name: 'usersTyping', properties: { rid: { type: 'string', indexed: true }, username: { type: 'string', optional: true } } }; const activeUsersSchema = { name: 'activeUsers', primaryKey: 'id', properties: { id: 'string', name: 'string?', username: 'string?', status: 'string?', utcOffset: 'double?' } }; const schema = [ settingsSchema, subscriptionSchema, messagesSchema, threadsSchema, threadMessagesSchema, usersSchema, roomsSchema, attachment, attachmentFields, messagesEditedBySchema, permissionsSchema, url, frequentlyUsedEmojiSchema, customEmojisSchema, messagesReactionsSchema, rolesSchema, uploadsSchema, slashCommandSchema, messagesTranslationsSchema ]; const inMemorySchema = [usersTypingSchema, activeUsersSchema]; class DB { databases = { serversDB: new Realm({ path: `${ RNRealmPath.realmPath }default.realm`, schema: [ userSchema, serversSchema ], schemaVersion: 10, migration: (oldRealm, newRealm) => { if (oldRealm.schemaVersion >= 1 && newRealm.schemaVersion <= 9) { const newServers = newRealm.objects('servers'); // eslint-disable-next-line no-plusplus for (let i = 0; i < newServers.length; i++) { newServers[i].roomsUpdatedAt = null; } } } }), inMemoryDB: new Realm({ path: `${ RNRealmPath.realmPath }memory.realm`, schema: inMemorySchema, schemaVersion: 2, inMemory: true }) } deleteAll(...args) { return this.database.write(() => this.database.deleteAll(...args)); } delete(...args) { return this.database.delete(...args); } write(...args) { return this.database.write(...args); } create(...args) { return this.database.create(...args); } objects(...args) { return this.database.objects(...args); } objectForPrimaryKey(...args) { return this.database.objectForPrimaryKey(...args); } get database() { return this.databases.activeDB; } get memoryDatabase() { return this.databases.inMemoryDB; } setActiveDB(database = '') { const path = database.replace(/(^\w+:|^)\/\//, ''); return this.databases.activeDB = new Realm({ path: `${ RNRealmPath.realmPath }${ path }.realm`, schema, schemaVersion: 13, migration: (oldRealm, newRealm) => { if (oldRealm.schemaVersion >= 3 && newRealm.schemaVersion <= 13) { const newSubs = newRealm.objects('subscriptions'); newRealm.delete(newSubs); const newMessages = newRealm.objects('messages'); newRealm.delete(newMessages); const newThreads = newRealm.objects('threads'); newRealm.delete(newThreads); const newThreadMessages = newRealm.objects('threadMessages'); newRealm.delete(newThreadMessages); } if (newRealm.schemaVersion === 9) { const newEmojis = newRealm.objects('customEmojis'); newRealm.delete(newEmojis); const newSettings = newRealm.objects('settings'); newRealm.delete(newSettings); } } }); } } const db = new DB(); export default db; // Realm workaround for "Cannot create asynchronous query while in a write transaction" // inpired from https://github.com/realm/realm-js/issues/1188#issuecomment-359223918 export function safeAddListener(results, callback, database = db) { if (!results || !results.addListener) { console.log('⚠️ safeAddListener called for non addListener-compliant object'); return; } if (database.isInTransaction) { setTimeout(() => { safeAddListener(results, callback); }, 50); } else { results.addListener(callback); } }