diff --git a/keyserver/src/scripts/test-threadstore-reductions.js b/keyserver/src/scripts/test-threadstore-reductions.js index 1dfc857a8..138ede4cb 100644 --- a/keyserver/src/scripts/test-threadstore-reductions.js +++ b/keyserver/src/scripts/test-threadstore-reductions.js @@ -1,287 +1,279 @@ // @flow import invariant from 'invariant'; import ashoat from 'lib/facts/ashoat.js'; import type { RawMessageInfo } from 'lib/types/message-types.js'; import type { ThreadPermission, ThreadPermissionsInfo, ThreadRolePermissionsBlob, } from 'lib/types/thread-permission-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import type { RawThreadInfos } from 'lib/types/thread-types.js'; import { hash } from 'lib/utils/objects.js'; import { main } from './utils.js'; import { fetchMessageInfos } from '../fetchers/message-fetchers.js'; import { fetchThreadInfos } from '../fetchers/thread-fetchers.js'; import { createScriptViewer } from '../session/scripts.js'; const viewer = createScriptViewer(ashoat.id); const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000; const messageSelectionCriteria = { joinedThreads: true, newerThan: oneWeekAgo }; async function testThreadStoreReductions() { const [{ threadInfos }, { rawMessageInfos }] = await Promise.all([ fetchThreadInfos(viewer), fetchMessageInfos(viewer, messageSelectionCriteria, 1), ]); const beforeReductions = JSON.stringify(threadInfos); const beforeBytes = new Blob([beforeReductions]).size; console.log( `before reductions, Ashoat's ThreadStore is ${beforeBytes} bytes large`, ); const withoutOldSidebars = filterOutOldSidebars(threadInfos, rawMessageInfos); - const withoutOldSidebarsString = JSON.stringify(withoutOldSidebars); - const withoutOldSidebarsBytes = new Blob([withoutOldSidebarsString]).size; - console.log( - "after filtering out old sidebars, Ashoat's ThreadStore is " + - `${withoutOldSidebarsBytes} bytes large`, - ); + testSolution(withoutOldSidebars, 'Ashoat', 'filtering out old sidebars'); const improvedEncoding = improveEncoding(threadInfos); - const improvedEncodingString = JSON.stringify(improvedEncoding); - const improvedEncodingBytes = new Blob([improvedEncodingString]).size; - console.log( - "after improving encoding, Ashoat's ThreadStore is " + - `${improvedEncodingBytes} bytes large`, - ); + testSolution(improvedEncoding, 'Ashoat', 'improving encoding'); const afterIsAdminRole = introduceIsAdminRole(threadInfos); - const afterIsAdminRoleString = JSON.stringify(afterIsAdminRole); - const afterIsAdminRoleBytes = new Blob([afterIsAdminRoleString]).size; - console.log( - "after introducing isAdminRole, Ashoat's ThreadStore is " + - `${afterIsAdminRoleBytes} bytes large`, - ); + testSolution(afterIsAdminRole, 'Ashoat', 'introducing isAdminRole'); const afterRoleStore = introduceThreadRolePermissionsStore(threadInfos); - const afterRoleStoreString = JSON.stringify(afterRoleStore); - const afterRoleStoreBytes = new Blob([afterRoleStoreString]).size; + testSolution( + afterRoleStore, + 'Ashoat', + 'introducing ThreadRolePermissionsStore', + ); +} + +function testSolution(blob: mixed, person: string, descriptor: string) { + const stringified = JSON.stringify(blob); + const numBytes = new Blob([stringified]).size; console.log( - "after introducing ThreadRolePermissionsStore, Ashoat's ThreadStore is " + - `${afterRoleStoreBytes} bytes large`, + `after ${descriptor}, ${person}'s ThreadStore is ${numBytes} bytes large`, ); } function filterOutOldSidebars( threadInfos: RawThreadInfos, messageInfos: $ReadOnlyArray, ): RawThreadInfos { const newestTimePerThread = new Map(); for (const messageInfo of messageInfos) { const { threadID, time } = messageInfo; const currentNewest = newestTimePerThread.get(threadID); if (!currentNewest || currentNewest < time) { newestTimePerThread.set(threadID, time); } } const filtered = {}; for (const threadID in threadInfos) { const threadInfo = threadInfos[threadID]; const latestUpdate = newestTimePerThread.get(threadID); if ( threadInfo.type !== threadTypes.SIDEBAR || (latestUpdate && latestUpdate >= oneWeekAgo) ) { filtered[threadID] = threadInfo; } } return filtered; } function improveEncoding(threadInfos: RawThreadInfos) { const simplifiedThreadStore = {}; for (const threadID in threadInfos) { const threadInfo = threadInfos[threadID]; const newRoles = {}; for (const roleID in threadInfo.roles) { const role = threadInfo.roles[roleID]; newRoles[roleID] = { ...role, permissions: improveThreadRolePermissionsBlobEncoding(role.permissions), }; } const smallerThreadInfo = { ...threadInfo, roles: newRoles, members: threadInfo.members.map(member => { const { permissions } = member; if (!permissions) { return member; } return { ...member, permissions: improveThreadPermissionsBlobEncoding(member.permissions), }; }), currentUser: { ...threadInfo.currentUser, permissions: improveThreadPermissionsBlobEncoding( threadInfo.currentUser.permissions, ), }, }; simplifiedThreadStore[threadID] = smallerThreadInfo; } return simplifiedThreadStore; } const smallerPermissionConstants = { ['know_of']: 'a', ['membership']: 'b', ['visible']: 'c', ['voiced']: 'd', ['edit_entries']: 'e', ['edit_thread']: 'f', ['edit_thread_description']: 'g', ['edit_thread_color']: 'h', ['delete_thread']: 'i', ['create_subthreads']: 'j', ['create_sidebars']: 'k', ['join_thread']: 'l', ['edit_permissions']: 'm', ['add_members']: 'n', ['remove_members']: 'o', ['change_role']: 'p', ['leave_thread']: 'q', ['react_to_message']: 'r', ['edit_message']: 's', ['edit_thread_avatar']: 't', ['manage_pins']: 'u', ['manage_invite_links']: 'v', }; function improveThreadPermissionsBlobEncoding(blob: ThreadPermissionsInfo) { const newBlob = {}; for (const permission in blob) { const permissionTyped: ThreadPermission = (permission: any); const oldValue = blob[permissionTyped].value; if (!oldValue) { continue; } const newKey = smallerPermissionConstants[permission]; invariant(newKey, `no newKey for ${permission}!`); newBlob[newKey] = 1; } return newBlob; } const smallerThreadPermissionPropagationPrefixes = { ['descendant_']: '1', ['child_']: '2', }; const smallerThreadPermissionFilterPrefixes = { ['open_']: '3', ['toplevel_']: '4', ['opentoplevel_']: '5', }; function improveThreadRolePermissionsBlobEncoding( blob: ThreadRolePermissionsBlob, ) { const newBlob = {}; for (const permission in blob) { const oldValue = blob[permission]; if (!oldValue) { continue; } let newKey = ''; let oldKey = permission; for (const prefix in smallerThreadPermissionPropagationPrefixes) { if (!oldKey.startsWith(prefix)) { continue; } oldKey = oldKey.substr(prefix.length); newKey += smallerThreadPermissionPropagationPrefixes[prefix]; } for (const prefix in smallerThreadPermissionFilterPrefixes) { if (!oldKey.startsWith(prefix)) { continue; } oldKey = oldKey.substr(prefix.length); newKey += smallerThreadPermissionFilterPrefixes[prefix]; } const newPermConstant = smallerPermissionConstants[oldKey]; invariant(newPermConstant, `no newPermConstant for ${oldKey}!`); newKey += newPermConstant; newBlob[newKey] = 1; } return newBlob; } function introduceIsAdminRole(threadInfos: RawThreadInfos) { const newThreadStore = {}; for (const threadID in threadInfos) { const threadInfo = threadInfos[threadID]; const newRoles = {}; for (const roleID in threadInfo.roles) { const role = threadInfo.roles[roleID]; let newRole = role; if (!newRole.isDefault) { const { isDefault, ...rest } = newRole; newRole = rest; } if (role.name === 'Admins') { newRole = { ...newRole, isAdmin: true, }; } newRoles[roleID] = newRole; } const newThreadInfo = { ...threadInfo, roles: newRoles, members: threadInfo.members.map(member => { const { permissions, ...rest } = member; return rest; }), }; newThreadStore[threadID] = newThreadInfo; } return newThreadStore; } function introduceThreadRolePermissionsStore(threadInfos: RawThreadInfos) { const newThreadStore = {}; const threadRolePermissionsStore = {}; for (const threadID in threadInfos) { const threadInfo = threadInfos[threadID]; const newRoles = {}; for (const roleID in threadInfo.roles) { const role = threadInfo.roles[roleID]; const rolePermissionsHash = hash(role.permissions); threadRolePermissionsStore[rolePermissionsHash] = role.permissions; newRoles[roleID] = { ...role, permissions: rolePermissionsHash, }; } const { permissions: currentUserPermissions, ...restCurrentUser } = threadInfo.currentUser; const newThreadInfo = { ...threadInfo, currentUser: restCurrentUser, roles: newRoles, members: threadInfo.members.map(member => { const { permissions, ...rest } = member; return rest; }), }; newThreadStore[threadID] = newThreadInfo; } return { threadInfos: newThreadStore, rolePermissions: threadRolePermissionsStore, }; } main([testThreadStoreReductions]);