@rimori/client 2.5.32 → 2.5.33

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.
Files changed (28) hide show
  1. package/dist/cli/scripts/init/dev-registration.js +118 -136
  2. package/dist/cli/scripts/init/main.js +116 -127
  3. package/dist/cli/scripts/init/package-setup.js +15 -2
  4. package/dist/cli/scripts/release/detect-translation-languages.js +24 -35
  5. package/dist/cli/scripts/release/release-config-upload.js +87 -100
  6. package/dist/cli/scripts/release/release-db-update.js +70 -81
  7. package/dist/cli/scripts/release/release-file-upload.js +75 -91
  8. package/dist/cli/scripts/release/release-prompts-upload.js +60 -72
  9. package/dist/cli/scripts/release/release.js +20 -31
  10. package/dist/controller/AccomplishmentController.js +12 -12
  11. package/dist/controller/AudioController.js +15 -33
  12. package/dist/controller/TranslationController.js +108 -118
  13. package/dist/fromRimori/EventBus.js +20 -31
  14. package/dist/plugin/CommunicationHandler.js +73 -81
  15. package/dist/plugin/Logger.js +71 -83
  16. package/dist/plugin/RimoriClient.js +31 -31
  17. package/dist/plugin/StandaloneClient.js +81 -98
  18. package/dist/plugin/TTS/ChunkedAudioPlayer.js +31 -41
  19. package/dist/plugin/TTS/MessageSender.js +28 -37
  20. package/dist/plugin/module/AIModule.js +215 -237
  21. package/dist/plugin/module/DbModule.js +22 -31
  22. package/dist/plugin/module/EventModule.js +23 -32
  23. package/dist/plugin/module/ExerciseModule.js +42 -56
  24. package/dist/plugin/module/PluginModule.js +97 -106
  25. package/dist/plugin/module/SharedContentController.js +170 -207
  26. package/dist/plugin/module/StorageModule.js +18 -29
  27. package/dist/worker/WorkerSetup.js +23 -34
  28. package/package.json +1 -1
@@ -1,12 +1,3 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  import { AccomplishmentController } from '../../controller/AccomplishmentController';
11
2
  import { EventBus } from '../../fromRimori/EventBus';
12
3
  /**
@@ -14,10 +5,14 @@ import { EventBus } from '../../fromRimori/EventBus';
14
5
  * Provides methods for emitting, listening to, and responding to events.
15
6
  */
16
7
  export class EventModule {
8
+ pluginId;
9
+ accomplishmentController;
10
+ aiModule;
11
+ eventBus;
17
12
  constructor(pluginId, aiModule, eventBus) {
18
13
  this.pluginId = pluginId;
19
14
  this.aiModule = aiModule;
20
- this.eventBus = eventBus !== null && eventBus !== void 0 ? eventBus : EventBus;
15
+ this.eventBus = eventBus ?? EventBus;
21
16
  this.accomplishmentController = new AccomplishmentController(pluginId, this.eventBus);
22
17
  // Listen for session token broadcasts from rimori-main (ExerciseSessionManager).
23
18
  // When an exercise starts: adopt the exercise token unconditionally.
@@ -33,7 +28,6 @@ export class EventModule {
33
28
  });
34
29
  }
35
30
  getGlobalEventTopic(preliminaryTopic) {
36
- var _a;
37
31
  if (preliminaryTopic.startsWith('global.')) {
38
32
  return preliminaryTopic;
39
33
  }
@@ -50,7 +44,7 @@ export class EventModule {
50
44
  else if (topicParts.length > 3) {
51
45
  throw new Error(`The event topic must consist of 3 parts. <pluginId>.<topic area>.<action>. Received: ${preliminaryTopic}`);
52
46
  }
53
- const topicRoot = (_a = this.pluginId) !== null && _a !== void 0 ? _a : 'global';
47
+ const topicRoot = this.pluginId ?? 'global';
54
48
  return `${topicRoot}.${preliminaryTopic}`;
55
49
  }
56
50
  /**
@@ -73,13 +67,10 @@ export class EventModule {
73
67
  * @param data The data to request.
74
68
  * @returns The response from the event.
75
69
  */
76
- request(topic, data) {
77
- return __awaiter(this, void 0, void 0, function* () {
78
- var _a;
79
- const globalTopic = this.getGlobalEventTopic(topic);
80
- const sessionToken = (_a = this.aiModule.session.get()) !== null && _a !== void 0 ? _a : undefined;
81
- return this.eventBus.request(this.pluginId, globalTopic, data, sessionToken);
82
- });
70
+ async request(topic, data) {
71
+ const globalTopic = this.getGlobalEventTopic(topic);
72
+ const sessionToken = this.aiModule.session.get() ?? undefined;
73
+ return this.eventBus.request(this.pluginId, globalTopic, data, sessionToken);
83
74
  }
84
75
  /**
85
76
  * Subscribe to an event.
@@ -113,14 +104,14 @@ export class EventModule {
113
104
  const topics = Array.isArray(topic) ? topic : [topic];
114
105
  let wrappedData = data;
115
106
  if (typeof data === 'function') {
116
- wrappedData = (event) => __awaiter(this, void 0, void 0, function* () {
107
+ wrappedData = async (event) => {
117
108
  const previousToken = this.aiModule.session.get();
118
109
  if (event.ai_session_token) {
119
110
  this.aiModule.session.set(event.ai_session_token);
120
111
  }
121
112
  try {
122
113
  // console.log('responding to event', event);
123
- return yield data(event);
114
+ return await data(event);
124
115
  }
125
116
  finally {
126
117
  if (event.ai_session_token) {
@@ -132,7 +123,7 @@ export class EventModule {
132
123
  }
133
124
  }
134
125
  }
135
- });
126
+ };
136
127
  }
137
128
  this.eventBus.respond(this.pluginId, topics.map((t) => this.getGlobalEventTopic(t)), wrappedData);
138
129
  }
@@ -140,10 +131,8 @@ export class EventModule {
140
131
  * Emit an accomplishment.
141
132
  * @param payload The payload to emit.
142
133
  */
143
- emitAccomplishment(payload) {
144
- return __awaiter(this, void 0, void 0, function* () {
145
- this.accomplishmentController.emitAccomplishment(payload);
146
- });
134
+ async emitAccomplishment(payload) {
135
+ this.accomplishmentController.emitAccomplishment(payload);
147
136
  }
148
137
  /**
149
138
  * Subscribe to an accomplishment.
@@ -169,7 +158,7 @@ export class EventModule {
169
158
  * @param params Optional additional parameters to pass to the action.
170
159
  */
171
160
  emitMainPanelAction(pluginId, actionKey, params) {
172
- this.emit('global.mainPanel.triggerAction', Object.assign({ plugin_id: pluginId, action_key: actionKey }, params));
161
+ this.emit('global.mainPanel.triggerAction', { plugin_id: pluginId, action_key: actionKey, ...params });
173
162
  }
174
163
  /**
175
164
  * Subscribe to main panel actions triggered by the user from the dashboard.
@@ -196,15 +185,17 @@ export class EventModule {
196
185
  */
197
186
  onMainPanelAction(callback, actionsToListen = []) {
198
187
  const listeningActions = Array.isArray(actionsToListen) ? actionsToListen : [actionsToListen];
199
- // this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
200
- this.emit('action.requestMain');
201
- return this.on('action.requestMain', ({ data }) => {
202
- // console.log('Received action for main panel ' + data.action_key);
203
- // console.log('Listening to actions', listeningActions);
188
+ // Register the listener BEFORE emitting the request so that synchronous responses
189
+ // from the federated bridge (in-process, not postMessage) are captured.
190
+ // In iframe mode the bridge is async (postMessage) so order didn't matter before,
191
+ // but in federated mode the respond fires synchronously during emit.
192
+ const listener = this.on('action.requestMain', ({ data }) => {
204
193
  if (listeningActions.length === 0 || listeningActions.includes(data.action_key)) {
205
194
  callback(data);
206
195
  }
207
196
  });
197
+ this.emit('action.requestMain');
198
+ return listener;
208
199
  }
209
200
  /**
210
201
  * Subscribe to side panel actions triggered by the user from the dashboard.
@@ -1,17 +1,11 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  /**
11
2
  * Controller for exercise-related operations.
12
3
  * Provides access to weekly exercises and exercise management.
13
4
  */
14
5
  export class ExerciseModule {
6
+ supabase;
7
+ communicationHandler;
8
+ eventModule;
15
9
  constructor(supabase, communicationHandler, _info, eventModule) {
16
10
  this.supabase = supabase;
17
11
  this.communicationHandler = communicationHandler;
@@ -22,14 +16,12 @@ export class ExerciseModule {
22
16
  * Shows exercises for the current week that haven't expired.
23
17
  * @returns Array of exercise objects.
24
18
  */
25
- view() {
26
- return __awaiter(this, void 0, void 0, function* () {
27
- const { data, error } = yield this.supabase.schema('public').from('weekly_exercises').select('*');
28
- if (error) {
29
- throw new Error(`Failed to fetch weekly exercises: ${error.message}`);
30
- }
31
- return data || [];
32
- });
19
+ async view() {
20
+ const { data, error } = await this.supabase.schema('public').from('weekly_exercises').select('*');
21
+ if (error) {
22
+ throw new Error(`Failed to fetch weekly exercises: ${error.message}`);
23
+ }
24
+ return data || [];
33
25
  }
34
26
  /**
35
27
  * Creates one or more exercises via the backend API.
@@ -38,21 +30,19 @@ export class ExerciseModule {
38
30
  * @param params Exercise creation parameters (single or array).
39
31
  * @returns Created exercise objects.
40
32
  */
41
- add(params) {
42
- return __awaiter(this, void 0, void 0, function* () {
43
- const exercises = Array.isArray(params) ? params : [params];
44
- const response = yield this.communicationHandler.fetchBackend('/exercises', {
45
- method: 'POST',
46
- body: JSON.stringify({ exercises }),
47
- });
48
- if (!response.ok) {
49
- const errorText = yield response.text();
50
- throw new Error(`Failed to create exercises: ${errorText}`);
51
- }
52
- const data = yield response.json();
53
- this.eventModule.emit('global.exercises.triggerChange');
54
- return data;
33
+ async add(params) {
34
+ const exercises = Array.isArray(params) ? params : [params];
35
+ const response = await this.communicationHandler.fetchBackend('/exercises', {
36
+ method: 'POST',
37
+ body: JSON.stringify({ exercises }),
55
38
  });
39
+ if (!response.ok) {
40
+ const errorText = await response.text();
41
+ throw new Error(`Failed to create exercises: ${errorText}`);
42
+ }
43
+ const data = await response.json();
44
+ this.eventModule.emit('global.exercises.triggerChange');
45
+ return data;
56
46
  }
57
47
  /**
58
48
  * Requests a new exercise session token from rimori-main.
@@ -66,22 +56,20 @@ export class ExerciseModule {
66
56
  * @param params.actionKey The action key identifying this exercise type.
67
57
  * @param params.knowledgeId Optional knowledge ID for tracking what was studied.
68
58
  */
69
- start(params) {
70
- return __awaiter(this, void 0, void 0, function* () {
71
- return new Promise((resolve, reject) => {
72
- const timeout = setTimeout(() => {
59
+ async start(params) {
60
+ return new Promise((resolve, reject) => {
61
+ const timeout = setTimeout(() => {
62
+ listener.off();
63
+ reject(new Error('Exercise start timed out: rimori-main did not respond within 5s'));
64
+ }, 5000);
65
+ const listener = this.eventModule.on('global.session.triggerUpdate', ({ data }) => {
66
+ if (data.session_token) {
67
+ clearTimeout(timeout);
73
68
  listener.off();
74
- reject(new Error('Exercise start timed out: rimori-main did not respond within 5s'));
75
- }, 5000);
76
- const listener = this.eventModule.on('global.session.triggerUpdate', ({ data }) => {
77
- if (data.session_token) {
78
- clearTimeout(timeout);
79
- listener.off();
80
- resolve();
81
- }
82
- });
83
- this.eventModule.emit('global.exercise.triggerStart', params);
69
+ resolve();
70
+ }
84
71
  });
72
+ this.eventModule.emit('global.exercise.triggerStart', params);
85
73
  });
86
74
  }
87
75
  /**
@@ -89,17 +77,15 @@ export class ExerciseModule {
89
77
  * @param id The exercise ID to delete.
90
78
  * @returns Success status.
91
79
  */
92
- delete(id) {
93
- return __awaiter(this, void 0, void 0, function* () {
94
- const response = yield this.communicationHandler.fetchBackend(`/exercises/${id}`, {
95
- method: 'DELETE',
96
- });
97
- if (!response.ok) {
98
- const errorText = yield response.text();
99
- throw new Error(`Failed to delete exercise: ${errorText}`);
100
- }
101
- this.eventModule.emit('global.exercises.triggerChange');
102
- return yield response.json();
80
+ async delete(id) {
81
+ const response = await this.communicationHandler.fetchBackend(`/exercises/${id}`, {
82
+ method: 'DELETE',
103
83
  });
84
+ if (!response.ok) {
85
+ const errorText = await response.text();
86
+ throw new Error(`Failed to delete exercise: ${errorText}`);
87
+ }
88
+ this.eventModule.emit('global.exercises.triggerChange');
89
+ return await response.json();
104
90
  }
105
91
  }
@@ -1,37 +1,38 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  import { Translator } from '../../controller/TranslationController';
11
2
  /**
12
3
  * Controller for plugin-related operations.
13
4
  * Provides access to plugin settings, user info, and plugin information.
14
5
  */
15
6
  export class PluginModule {
7
+ pluginId;
8
+ rimoriInfo;
9
+ translator;
10
+ supabase;
11
+ communicationHandler;
12
+ /**
13
+ * The release channel of this plugin installation.
14
+ * Determines which database schema is used for plugin tables.
15
+ */
16
+ releaseChannel;
17
+ applicationMode;
18
+ theme;
19
+ /**
20
+ * Whether text-to-speech is globally enabled (set in rimori-main navbar).
21
+ * Updated automatically when the user toggles TTS.
22
+ */
23
+ ttsEnabled = true;
16
24
  constructor(supabase, communicationHandler, info, ai) {
17
- var _a;
18
- /**
19
- * Whether text-to-speech is globally enabled (set in rimori-main navbar).
20
- * Updated automatically when the user toggles TTS.
21
- */
22
- this.ttsEnabled = true;
23
25
  this.rimoriInfo = info;
24
26
  this.supabase = supabase;
25
27
  this.pluginId = info.pluginId;
26
28
  this.releaseChannel = info.releaseChannel;
27
29
  this.communicationHandler = communicationHandler;
28
30
  const currentPlugin = info.installedPlugins.find((plugin) => plugin.id === info.pluginId);
29
- this.translator = new Translator(info.interfaceLanguage, (currentPlugin === null || currentPlugin === void 0 ? void 0 : currentPlugin.endpoint) || '', ai);
30
- this.ttsEnabled = (_a = info.ttsEnabled) !== null && _a !== void 0 ? _a : true;
31
+ this.translator = new Translator(info.interfaceLanguage, currentPlugin?.endpoint || '', ai);
32
+ this.ttsEnabled = info.ttsEnabled ?? true;
31
33
  this.communicationHandler.onUpdate((updatedInfo) => {
32
- var _a;
33
34
  this.rimoriInfo = updatedInfo;
34
- this.ttsEnabled = (_a = updatedInfo.ttsEnabled) !== null && _a !== void 0 ? _a : true;
35
+ this.ttsEnabled = updatedInfo.ttsEnabled ?? true;
35
36
  });
36
37
  this.applicationMode = this.communicationHandler.getQueryParam('applicationMode');
37
38
  this.theme = this.communicationHandler.getQueryParam('rm_theme') || 'light';
@@ -42,20 +43,17 @@ export class PluginModule {
42
43
  * Otherwise, fetches user-specific settings.
43
44
  * @returns The settings object or null if not found.
44
45
  */
45
- fetchSettings() {
46
- return __awaiter(this, void 0, void 0, function* () {
47
- var _a;
48
- const isGuildSetting = !this.rimoriInfo.guild.allowUserPluginSettings;
49
- const { data } = yield this.supabase
50
- .schema('public')
51
- .from('plugin_settings')
52
- .select('*')
53
- .eq('plugin_id', this.pluginId)
54
- .eq('guild_id', this.rimoriInfo.guild.id)
55
- .eq('is_guild_setting', isGuildSetting)
56
- .maybeSingle();
57
- return (_a = data === null || data === void 0 ? void 0 : data.settings) !== null && _a !== void 0 ? _a : null;
58
- });
46
+ async fetchSettings() {
47
+ const isGuildSetting = !this.rimoriInfo.guild.allowUserPluginSettings;
48
+ const { data } = await this.supabase
49
+ .schema('public')
50
+ .from('plugin_settings')
51
+ .select('*')
52
+ .eq('plugin_id', this.pluginId)
53
+ .eq('guild_id', this.rimoriInfo.guild.id)
54
+ .eq('is_guild_setting', isGuildSetting)
55
+ .maybeSingle();
56
+ return data?.settings ?? null;
59
57
  }
60
58
  /**
61
59
  * Sets settings for the plugin.
@@ -64,82 +62,77 @@ export class PluginModule {
64
62
  * @param settings - The settings object to save.
65
63
  * @throws {Error} if RLS blocks the operation.
66
64
  */
67
- setSettings(settings) {
68
- return __awaiter(this, void 0, void 0, function* () {
69
- var _a;
70
- const isGuildSetting = !this.rimoriInfo.guild.allowUserPluginSettings;
71
- const payload = {
72
- plugin_id: this.pluginId,
73
- settings,
74
- guild_id: this.rimoriInfo.guild.id,
75
- is_guild_setting: isGuildSetting,
76
- };
77
- if (isGuildSetting) {
78
- payload.user_id = null;
79
- }
80
- // Try UPDATE first (safe with RLS). If nothing updated, INSERT.
81
- const updateQuery = this.supabase
82
- .schema('public')
83
- .from('plugin_settings')
84
- .update({ settings })
85
- .eq('plugin_id', this.pluginId)
86
- .eq('guild_id', this.rimoriInfo.guild.id)
87
- .eq('is_guild_setting', isGuildSetting);
88
- const { data: updatedRows, error: updateError } = yield (isGuildSetting
89
- ? updateQuery.is('user_id', null).select()
90
- : updateQuery.select());
91
- if (updateError) {
92
- if (updateError.code === '42501' || ((_a = updateError.message) === null || _a === void 0 ? void 0 : _a.includes('policy'))) {
93
- throw new Error(`Cannot set ${isGuildSetting ? 'guild' : 'user'} settings: Permission denied.`);
94
- }
95
- // proceed to try insert in case of other issues
96
- }
97
- if (updatedRows && updatedRows.length > 0) {
98
- return; // updated successfully
65
+ async setSettings(settings) {
66
+ const isGuildSetting = !this.rimoriInfo.guild.allowUserPluginSettings;
67
+ const payload = {
68
+ plugin_id: this.pluginId,
69
+ settings,
70
+ guild_id: this.rimoriInfo.guild.id,
71
+ is_guild_setting: isGuildSetting,
72
+ };
73
+ if (isGuildSetting) {
74
+ payload.user_id = null;
75
+ }
76
+ // Try UPDATE first (safe with RLS). If nothing updated, INSERT.
77
+ const updateQuery = this.supabase
78
+ .schema('public')
79
+ .from('plugin_settings')
80
+ .update({ settings })
81
+ .eq('plugin_id', this.pluginId)
82
+ .eq('guild_id', this.rimoriInfo.guild.id)
83
+ .eq('is_guild_setting', isGuildSetting);
84
+ const { data: updatedRows, error: updateError } = await (isGuildSetting
85
+ ? updateQuery.is('user_id', null).select()
86
+ : updateQuery.select());
87
+ if (updateError) {
88
+ if (updateError.code === '42501' || updateError.message?.includes('policy')) {
89
+ throw new Error(`Cannot set ${isGuildSetting ? 'guild' : 'user'} settings: Permission denied.`);
99
90
  }
100
- // No row updated -> INSERT
101
- const { error: insertError } = yield this.supabase.schema('public').from('plugin_settings').insert(payload);
102
- if (insertError) {
103
- // In case of race condition (duplicate), try one more UPDATE
104
- if (insertError.code === '23505' /* unique_violation */) {
105
- const retry = this.supabase
106
- .schema('public')
107
- .from('plugin_settings')
108
- .update({ settings })
109
- .eq('plugin_id', this.pluginId)
110
- .eq('guild_id', this.rimoriInfo.guild.id)
111
- .eq('is_guild_setting', isGuildSetting);
112
- const { error: retryError } = yield (isGuildSetting ? retry.is('user_id', null) : retry);
113
- if (!retryError)
114
- return;
115
- }
116
- throw insertError;
91
+ // proceed to try insert in case of other issues
92
+ }
93
+ if (updatedRows && updatedRows.length > 0) {
94
+ return; // updated successfully
95
+ }
96
+ // No row updated -> INSERT
97
+ const { error: insertError } = await this.supabase.schema('public').from('plugin_settings').insert(payload);
98
+ if (insertError) {
99
+ // In case of race condition (duplicate), try one more UPDATE
100
+ if (insertError.code === '23505' /* unique_violation */) {
101
+ const retry = this.supabase
102
+ .schema('public')
103
+ .from('plugin_settings')
104
+ .update({ settings })
105
+ .eq('plugin_id', this.pluginId)
106
+ .eq('guild_id', this.rimoriInfo.guild.id)
107
+ .eq('is_guild_setting', isGuildSetting);
108
+ const { error: retryError } = await (isGuildSetting ? retry.is('user_id', null) : retry);
109
+ if (!retryError)
110
+ return;
117
111
  }
118
- });
112
+ throw insertError;
113
+ }
119
114
  }
120
115
  /**
121
116
  * Get the settings for the plugin. T can be any type of settings, UserSettings or SystemSettings.
122
117
  * @param defaultSettings The default settings to use if no settings are found.
123
118
  * @returns The settings for the plugin.
124
119
  */
125
- getSettings(defaultSettings) {
126
- return __awaiter(this, void 0, void 0, function* () {
127
- const storedSettings = yield this.fetchSettings();
128
- if (!storedSettings) {
129
- yield this.setSettings(defaultSettings);
130
- return defaultSettings;
131
- }
132
- // Handle settings migration
133
- const storedKeys = Object.keys(storedSettings);
134
- const defaultKeys = Object.keys(defaultSettings);
135
- if (storedKeys.length !== defaultKeys.length) {
136
- const validStoredSettings = Object.fromEntries(Object.entries(storedSettings).filter(([key]) => defaultKeys.includes(key)));
137
- const mergedSettings = Object.assign(Object.assign({}, defaultSettings), validStoredSettings);
138
- yield this.setSettings(mergedSettings);
139
- return mergedSettings;
140
- }
141
- return storedSettings;
142
- });
120
+ async getSettings(defaultSettings) {
121
+ const storedSettings = await this.fetchSettings();
122
+ if (!storedSettings) {
123
+ await this.setSettings(defaultSettings);
124
+ return defaultSettings;
125
+ }
126
+ // Handle settings migration
127
+ const storedKeys = Object.keys(storedSettings);
128
+ const defaultKeys = Object.keys(defaultSettings);
129
+ if (storedKeys.length !== defaultKeys.length) {
130
+ const validStoredSettings = Object.fromEntries(Object.entries(storedSettings).filter(([key]) => defaultKeys.includes(key)));
131
+ const mergedSettings = { ...defaultSettings, ...validStoredSettings };
132
+ await this.setSettings(mergedSettings);
133
+ return mergedSettings;
134
+ }
135
+ return storedSettings;
143
136
  }
144
137
  /**
145
138
  * Get the current user info.
@@ -183,11 +176,9 @@ export class PluginModule {
183
176
  * Get the translator for the plugin.
184
177
  * @returns The translator for the plugin.
185
178
  */
186
- getTranslator() {
187
- return __awaiter(this, void 0, void 0, function* () {
188
- yield this.translator.initialize();
189
- return this.translator;
190
- });
179
+ async getTranslator() {
180
+ await this.translator.initialize();
181
+ return this.translator;
191
182
  }
192
183
  }
193
184
  /** Ordered tiers from lowest to highest access level */