-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtbstorage.js.html
630 lines (545 loc) · 31.5 KB
/
tbstorage.js.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>tbstorage.js - Documentation</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
<script src="scripts/nav.js" defer></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav >
<input type="text" id="nav-search" placeholder="Search" />
<h2><a href="index.html">Home</a></h2><h2><a href="https://github.com/toolbox-team/reddit-moderator-toolbox" target="_blank" class="menu-item" >Github</a></h2><h2><a href="https://www.reddit.com/r/toolbox" target="_blank" class="menu-item" >Subreddit</a></h2><h3>Classes</h3><ul><li><a href="Module.html">Module</a><ul class='methods'><li data-type='method'><a href="Module.html#get">get</a></li><li data-type='method'><a href="Module.html#getEnabled">getEnabled</a></li><li data-type='method'><a href="Module.html#init">init</a></li><li data-type='method'><a href="Module.html#set">set</a></li><li data-type='method'><a href="Module.html#setEnabled">setEnabled</a></li></ul></li><li><a href="TBListener.html">TBListener</a><ul class='methods'><li data-type='method'><a href="TBListener.html#clear">clear</a></li><li data-type='method'><a href="TBListener.html#on">on</a></li><li data-type='method'><a href="TBListener.html#start">start</a></li><li data-type='method'><a href="TBListener.html#stop">stop</a></li></ul></li></ul><h3>Global</h3><ul><li><a href="global.html#DISPLAY_BOTTOM">DISPLAY_BOTTOM</a></li><li><a href="global.html#DISPLAY_CENTER">DISPLAY_CENTER</a></li><li><a href="global.html#FEEDBACK_NEGATIVE">FEEDBACK_NEGATIVE</a></li><li><a href="global.html#FEEDBACK_NEUTRAL">FEEDBACK_NEUTRAL</a></li><li><a href="global.html#FEEDBACK_POSITIVE">FEEDBACK_POSITIVE</a></li><li><a href="global.html#ModNotesBadge">ModNotesBadge</a></li><li><a href="global.html#ModNotesPager">ModNotesPager</a></li><li><a href="global.html#ModNotesPopup">ModNotesPopup</a></li><li><a href="global.html#NoteTableRow">NoteTableRow</a></li><li><a href="global.html#RandomFeedback">RandomFeedback</a></li><li><a href="global.html#RandomQuote">RandomQuote</a></li><li><a href="global.html#TBsettingsObject">TBsettingsObject</a></li><li><a href="global.html#actionButton">actionButton</a></li><li><a href="global.html#addModSubsToSidebar">addModSubsToSidebar</a></li><li><a href="global.html#addToSiteTable">addToSiteTable</a></li><li><a href="global.html#addTrophiesToSidebar">addTrophiesToSidebar</a></li><li><a href="global.html#alert">alert</a></li><li><a href="global.html#baseDomain">baseDomain</a></li><li><a href="global.html#browserName">browserName</a></li><li><a href="global.html#buildSha">buildSha</a></li><li><a href="global.html#buildType">buildType</a></li><li><a href="global.html#button">button</a></li><li><a href="global.html#checkForActions">checkForActions</a></li><li><a href="global.html#cleanSubredditName">cleanSubredditName</a></li><li><a href="global.html#clearCache">clearCache</a></li><li><a href="global.html#colorNameToHex">colorNameToHex</a></li><li><a href="global.html#contextTrigger">contextTrigger</a></li><li><a href="global.html#createDeferredProcessQueue">createDeferredProcessQueue</a></li><li><a href="global.html#daysToMilliseconds">daysToMilliseconds</a></li><li><a href="global.html#debounce">debounce</a></li><li><a href="global.html#debugInformation">debugInformation</a></li><li><a href="global.html#defaultNoteLabelValueToLabelType">defaultNoteLabelValueToLabelType</a></li><li><a href="global.html#delay">delay</a></li><li><a href="global.html#displayNotes">displayNotes</a></li><li><a href="global.html#domain">domain</a></li><li><a href="global.html#escapeHTML">escapeHTML</a></li><li><a href="global.html#fetchModSubs">fetchModSubs</a></li><li><a href="global.html#fetchNewsNotes">fetchNewsNotes</a></li><li><a href="global.html#figureOutMulti">figureOutMulti</a></li><li><a href="global.html#filterModdable">filterModdable</a></li><li><a href="global.html#getActions">getActions</a></li><li><a href="global.html#getAllModNotes">getAllModNotes</a></li><li><a href="global.html#getAnonymizedSettings">getAnonymizedSettings</a></li><li><a href="global.html#getCache">getCache</a></li><li><a href="global.html#getContextURL">getContextURL</a></li><li><a href="global.html#getLastVersion">getLastVersion</a></li><li><a href="global.html#getLatestModNote">getLatestModNote</a></li><li><a href="global.html#getModSubs">getModSubs</a></li><li><a href="global.html#getModlog">getModlog</a></li><li><a href="global.html#getRandomNumber">getRandomNumber</a></li><li><a href="global.html#getSetting">getSetting</a></li><li><a href="global.html#getSettingAsync">getSettingAsync</a></li><li><a href="global.html#getSettings">getSettings</a></li><li><a href="global.html#getSubmissionFullname">getSubmissionFullname</a></li><li><a href="global.html#getSubredditColors">getSubredditColors</a></li><li><a href="global.html#getTime">getTime</a></li><li><a href="global.html#getToolboxDevs">getToolboxDevs</a></li><li><a href="global.html#handleMessage">handleMessage</a></li><li><a href="global.html#handleTBThings">handleTBThings</a></li><li><a href="global.html#handleThing">handleThing</a></li><li><a href="global.html#hideModActionsThings">hideModActionsThings</a></li><li><a href="global.html#htmlDecode">htmlDecode</a></li><li><a href="global.html#htmlEncode">htmlEncode</a></li><li><a href="global.html#humaniseDays">humaniseDays</a></li><li><a href="global.html#init">init</a></li><li><a href="global.html#initialLoadPromise">initialLoadPromise</a></li><li><a href="global.html#isConfigValidVersion">isConfigValidVersion</a></li><li><a href="global.html#isEquivalent">isEquivalent</a></li><li><a href="global.html#isModSub">isModSub</a></li><li><a href="global.html#isNewModmail">isNewModmail</a></li><li><a href="global.html#isOldReddit">isOldReddit</a></li><li><a href="global.html#labelColors">labelColors</a></li><li><a href="global.html#labelNames">labelNames</a></li><li><a href="global.html#link">link</a></li><li><a href="global.html#listenerAliases">listenerAliases</a></li><li><a href="global.html#literalRegExp">literalRegExp</a></li><li><a href="global.html#makeCommentThread">makeCommentThread</a></li><li><a href="global.html#makeQueueOverlay">makeQueueOverlay</a></li><li><a href="global.html#makeSingleComment">makeSingleComment</a></li><li><a href="global.html#makeSubmissionEntry">makeSubmissionEntry</a></li><li><a href="global.html#makeUserSidebar">makeUserSidebar</a></li><li><a href="global.html#messageHandlers">messageHandlers</a></li><li><a href="global.html#millisecondsToDays">millisecondsToDays</a></li><li><a href="global.html#minutesToMilliseconds">minutesToMilliseconds</a></li><li><a href="global.html#modbarExists">modbarExists</a></li><li><a href="global.html#moveArrayItem">moveArrayItem</a></li><li><a href="global.html#newModmailSidebar">newModmailSidebar</a></li><li><a href="global.html#niceDateDiff">niceDateDiff</a></li><li><a href="global.html#notification">notification</a></li><li><a href="global.html#overlay">overlay</a></li><li><a href="global.html#pager">pager</a></li><li><a href="global.html#pagerForItems">pagerForItems</a></li><li><a href="global.html#parseComments">parseComments</a></li><li><a href="global.html#parser">parser</a></li><li><a href="global.html#popup">popup</a></li><li><a href="global.html#progressivePager">progressivePager</a></li><li><a href="global.html#purify">purify</a></li><li><a href="global.html#purifyObject">purifyObject</a></li><li><a href="global.html#regExpEscape">regExpEscape</a></li><li><a href="global.html#relativeTime">relativeTime</a></li><li><a href="global.html#reloadIframe">reloadIframe</a></li><li><a href="global.html#reloadToolbox">reloadToolbox</a></li><li><a href="global.html#remove">remove</a></li><li><a href="global.html#removeLastDirectoryPartOf">removeLastDirectoryPartOf</a></li><li><a href="global.html#removeQuotes">removeQuotes</a></li><li><a href="global.html#replaceAll">replaceAll</a></li><li><a href="global.html#replaceTokens">replaceTokens</a></li><li><a href="global.html#saneSort">saneSort</a></li><li><a href="global.html#saneSortAs">saneSortAs</a></li><li><a href="global.html#saveSettingsToBrowser">saveSettingsToBrowser</a></li><li><a href="global.html#searchProfile">searchProfile</a></li><li><a href="global.html#setCache">setCache</a></li><li><a href="global.html#setSetting">setSetting</a></li><li><a href="global.html#setSettingAsync">setSettingAsync</a></li><li><a href="global.html#settings">settings</a></li><li><a href="global.html#settingsToObject">settingsToObject</a></li><li><a href="global.html#shortVersion">shortVersion</a></li><li><a href="global.html#showNote">showNote</a></li><li><a href="global.html#sortBy">sortBy</a></li><li><a href="global.html#standardColors">standardColors</a></li><li><a href="global.html#stringToColor">stringToColor</a></li><li><a href="global.html#submissionFullnamesCache">submissionFullnamesCache</a></li><li><a href="global.html#tbRedditEvent">tbRedditEvent</a></li><li><a href="global.html#textFeedback">textFeedback</a></li><li><a href="global.html#timeConverterRead">timeConverterRead</a></li><li><a href="global.html#title_to_url">title_to_url</a></li><li><a href="global.html#toolboxVersion">toolboxVersion</a></li><li><a href="global.html#toolboxVersionName">toolboxVersionName</a></li><li><a href="global.html#typeNames">typeNames</a></li><li><a href="global.html#unescapeHTML">unescapeHTML</a></li><li><a href="global.html#unescapeJSON">unescapeJSON</a></li><li><a href="global.html#verifiedSettingsSave">verifiedSettingsSave</a></li><li><a href="global.html#watchForURLChanges">watchForURLChanges</a></li><li><a href="global.html#wrapWithLastValue">wrapWithLastValue</a></li><li><a href="global.html#zlibDeflate">zlibDeflate</a></li><li><a href="global.html#zlibInflate">zlibInflate</a></li></ul>
</nav>
<div id="main">
<h1 class="page-title">tbstorage.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>import DOMPurify from 'dompurify';
import $ from 'jquery';
import browser from 'webextension-polyfill';
import TBLog from './tblog.ts';
const logger = TBLog('TBStorage');
/**
* The current subdomain (NOT full domain).
* @type {string}
*/
export const domain = window.location.hostname.split('.')[0];
logger.debug(`Domain: ${domain}`);
/**
* A list of all currently loaded settings keys.
* @type {string[]}
*/
export const settings = [];
/**
* An object mapping setting keys to their currently loaded values.
* @type {{[key: string]: any}}
*/
let TBsettingsObject;
/**
* A promise which will fulfill once the current settings are fetched from
* extension storage. Once this promise fulfills, it's safe to assume
* `TBsettingsObject` contains our settings. Settings reads are delayed until
* then - either by awaiting this promise, or by waiting for the
* `TBStorageLoaded` window event, which is emitted at the same time.
*/
const initialLoadPromise = browser.storage.local.get('tbsettings').then(sObject => {
if (sObject.tbsettings) {
TBsettingsObject = sObject.tbsettings;
// Paranoid, malicious settings might be stored.
purifyObject(TBsettingsObject);
} else {
TBsettingsObject = {};
}
// Once that's all done, this promise will fulfill and pending storage calls
// will be unblocked.
});
// Don't handle background page messages until we've got our initial settings.
initialLoadPromise.then(() => {
// Listen for updated settings and update the settings object.
browser.runtime.onMessage.addListener(message => {
// A complete settings object. Likely because settings have been saved or imported. Make sure to notify the user if they have settings open in this tab.
if (message.action === 'tb-settings-update') {
TBsettingsObject = message.payload.tbsettings;
const $body = $('body');
$body.find('.tb-window-footer').addClass('tb-footer-save-warning');
$('body').find('.tb-personal-settings .tb-save').before(
'<div class="tb-save-warning">Settings have been saved in a different browser tab! Saving from this window will overwrite those settings.</div>',
);
}
// Single setting. Usually reserved for background operations as such we'll simply update it in the backround.
if (message.action === 'tb-single-setting-update') {
TBsettingsObject[message.payload.key] = message.payload.value;
const keySplit = message.payload.key.split('.');
const detailObject = {
module: keySplit[1],
setting: keySplit[2],
value: message.payload.value,
};
window.dispatchEvent(
new CustomEvent('tbSingleSettingUpdate', {
detail: detailObject,
}),
);
}
});
});
/**
* Generates an anonymized version of the settings object, with some sensitive
* settings omitted and other settings represented differently.
* @returns {Promise<object>}
*/
export const getAnonymizedSettings = () =>
new Promise(resolve => {
settingsToObject(sObject => {
// settings we delete
delete sObject['Toolbox.Achievements.lastSeen'];
delete sObject['Toolbox.Achievements.last_seen'];
delete sObject['Toolbox.Bagels.bagelType'];
delete sObject['Toolbox.Bagels.enabled'];
delete sObject['Toolbox.Modbar.customCSS'];
delete sObject['Toolbox.ModMail.lastVisited'];
delete sObject['Toolbox.ModMail.replied'];
delete sObject['Toolbox.ModMail.subredditColorSalt'];
delete sObject['Toolbox.Notifier.lastChecked'];
delete sObject['Toolbox.Notifier.lastSeenModmail'];
delete sObject['Toolbox.Notifier.lastSeenUnmoderated'];
delete sObject['Toolbox.Notifier.modmailCount'];
delete sObject['Toolbox.Notifier.modqueueCount'];
delete sObject['Toolbox.Notifier.modqueuePushed'];
delete sObject['Toolbox.Notifier.unmoderatedCount'];
delete sObject['Toolbox.Notifier.unreadMessageCount'];
delete sObject['Toolbox.Notifier.unreadPushed'];
delete sObject['Toolbox.QueueTools.kitteh'];
delete sObject['Toolbox.RReasons.customRemovalReason'];
delete sObject['Toolbox.Snoo.enabled'];
delete sObject['Toolbox.Storage.settings'];
delete sObject['Toolbox.Utils.echoTest'];
delete sObject['Toolbox.Utils.tbDevs'];
// these settings we want the length of the val.
sObject['Toolbox.Comments.highlighted'] = undefindedOrLength(sObject['Toolbox.Comments.highlighted']);
sObject['Toolbox.ModButton.savedSubs'] = undefindedOrLength(sObject['Toolbox.ModButton.savedSubs']);
sObject['Toolbox.ModMail.botsToFilter'] = undefindedOrLength(sObject['Toolbox.ModMail.botsToFilter']);
sObject['Toolbox.ModMail.filteredSubs'] = undefindedOrLength(sObject['Toolbox.ModMail.filteredSubs']);
sObject['Toolbox.Modbar.shortcuts'] = undefindedOrLength(sObject['Toolbox.Modbar.shortcuts']);
sObject['Toolbox.QueueTools.botCheckmark'] = undefindedOrLength(sObject['Toolbox.QueueTools.botCheckmark']);
sObject['Toolbox.Utils.seenNotes'] = undefindedOrLength(sObject['Toolbox.Utils.seenNotes']);
// these settings we just want to know if they are populated at all
sObject['Toolbox.Achievements.save'] = undefindedOrTrue(sObject['Toolbox.Achievements.save']);
sObject['Toolbox.ModButton.lastAction'] = undefindedOrTrue(sObject['Toolbox.ModButton.lastAction']);
sObject['Toolbox.Modbar.lastExport'] = undefindedOrTrue(sObject['Toolbox.Modbar.lastExport']);
sObject['Toolbox.Notifier.modSubreddits'] = undefindedOrTrue(sObject['Toolbox.Notifier.modSubreddits']);
sObject['Toolbox.Notifier.modmailSubreddits'] = undefindedOrTrue(
sObject['Toolbox.Notifier.modmailSubreddits'],
);
sObject['Toolbox.Notifier.unmoderatedSubreddits'] = undefindedOrTrue(
sObject['Toolbox.Notifier.unmoderatedSubreddits'],
);
sObject['Toolbox.PNotes.noteWiki'] = undefindedOrTrue(sObject['Toolbox.PNotes.noteWiki']);
sObject['Toolbox.QueueTools.queueCreature'] = undefindedOrTrue(sObject['Toolbox.QueueTools.queueCreature']);
sObject['Toolbox.QueueTools.subredditColorSalt'] = undefindedOrTrue(
sObject['Toolbox.QueueTools.subredditColorSalt'],
);
sObject['Toolbox.Utils.settingSub'] = undefindedOrTrue(sObject['Toolbox.Utils.settingSub']);
resolve(sObject);
function undefindedOrLength (setting) {
return setting === undefined ? 0 : setting.length;
}
function undefindedOrTrue (setting) {
if (!setting) {
return false;
}
if (setting.length > 0) {
return true;
}
}
});
});
/**
* Clears all cache keys.
* @returns {Promise<void>}
*/
export async function clearCache () {
await browser.runtime.sendMessage({
action: 'tb-cache',
method: 'clear',
});
}
// The below block of code will keep watch for events that require clearing the cache like account switching and people accepting mod invites.
$('body').on(
'click',
'#RESAccountSwitcherDropdown .accountName, #header-bottom-right .logout, .toggle.moderator .option',
() => {
clearCache();
},
);
/**
* Saves current settings, then verifies that they've been saved accurately. If
* the save was successful, tells the background page to update the settings
* state of other tabs. Calls back with whether or not the save was successful.
* @param {(success: boolean) => void} callback
*/
export function verifiedSettingsSave (callback) {
settingsToObject(sObject => {
const settingsObject = sObject;
// save settings
browser.storage.local.set({
tbsettings: sObject,
}).then(() => {
// now verify them
browser.storage.local.get('tbsettings').then(returnObject => {
if (returnObject.tbsettings && isEquivalent(returnObject.tbsettings, settingsObject)) {
// Succes, tell other browser tabs with toolbox that there are new settings.
browser.runtime.sendMessage({
action: 'tb-global',
globalEvent: 'tb-settings-update',
payload: returnObject,
excludeBackground: true,
});
callback(true);
} else {
logger.debug('Settings could not be verified');
callback(false);
}
});
});
});
}
/**
* Uses DOMPurify to sanitize untrusted HTML strings.
* @param {string} input
* @returns {string}
*/
export function purify (input) {
return DOMPurify.sanitize(input, {SAFE_FOR_JQUERY: true});
}
// TODO: to be honest I'm not sure what this one does
function registerSetting (module, setting) {
// First parse out any of the ones we never want to save.
if (module === undefined || module === 'cache') {
return;
}
const keyName = `${module}.${setting}`;
if (!settings.includes(keyName)) {
settings.push(keyName);
}
}
/**
* Recursively sanitize an object's string values as untrusted HTML. String
* values that can be interpreted as JSON objects are parsed, sanitized, and
* re-stringified.
* @param {any} input
*/
export function purifyObject (input) {
for (const key in input) {
if (Object.prototype.hasOwnProperty.call(input, key)) {
const itemType = typeof input[key];
switch (itemType) {
case 'object':
purifyObject(input[key]);
break;
case 'string':
// If the string we're handling is a JSON string, purifying it before it's parsed will mangle
// the JSON and make it unusable. We try to parse every value, and if parsing returns an object
// or an array, we run purifyObject on the result and re-stringify the value, rather than
// trying to purify the string itself. This ensures that when the string is parsed somewhere
// else, it's already purified.
// TODO: Identify if this behavior is actually used anywhere
try {
const jsonObject = JSON.parse(input[key]);
// We only want to purify the parsed value if it's an object or array, otherwise we throw
// back and purify the raw string instead (see #461)
if (typeof jsonObject !== 'object' || jsonObject == null) {
throw new Error('not using the parsed result of this string');
}
purifyObject(jsonObject);
input[key] = JSON.stringify(jsonObject);
} catch (e) {
// Not json, simply purify
input[key] = purify(input[key]);
}
break;
case 'function':
// If we are dealing with an actual function something is really wrong and we'll overwrite it.
input[key] = 'function';
break;
case 'number':
case 'boolean':
case 'undefined':
// Do nothing with these as they are supposed to be safe.
break;
default:
// If we end here we are dealing with a type we don't expect to begin with. Begone!
input[key] = `unknown item type ${itemType}`;
}
}
}
}
// TODO: this is another purify function used exclusively for settings, I'm not
// sure how it works either.
function purifyThing (input) {
let output;
const itemType = typeof input;
switch (itemType) {
case 'object':
purifyObject(input);
output = input;
break;
case 'string':
// Let's see if we are dealing with json.
// We want to handle json properly otherwise the purify process will mess up things.
try {
const jsonObject = JSON.parse(input);
purifyObject(jsonObject);
output = JSON.stringify(jsonObject);
} catch (e) {
// Not json, simply purify
output = purify(input);
}
break;
case 'function':
// If we are dealing with an actual function something is really wrong and we'll overwrite it.
output = 'function';
break;
case 'number':
case 'boolean':
case 'undefined':
// Do nothing with these as they are supposed to be safe.
output = input;
break;
default:
// If we end here we are dealing with a type we don't expect to begin with. Begone!
output = `unknown item type ${itemType}`;
}
return output;
}
/**
* Calls back with a copy of the current settings object, containing all setting
* keys and their values.
* @param {(settings: object) => void} callback
*/
function settingsToObject (callback) {
initialLoadPromise.then(() => {
// We make a deep clone of the settings object so it can safely be used and manipulated for things like anonymized exports.
const settingsObject = JSON.parse(JSON.stringify(TBsettingsObject));
// We are paranoid, so we are going to purify the object first.s
purifyObject(settingsObject);
callback(settingsObject);
});
}
/**
* Promises a copy of the current settings object, containing all setting keys
* and their values.
* @returns {Promise<object>}
*/
// TODO: convert original function to promise
export const getSettings = () => new Promise(resolve => settingsToObject(resolve));
/**
* Commits the current settings to extension storage.
* @returns {Promise<void>}
*/
async function saveSettingsToBrowser () {
browser.storage.local.set({
tbsettings: await getSettings(),
});
}
/**
* Returns the value of a setting.
* @deprecated Use `getSettingAsync` instead
* @param {string} module The ID of the module the setting belongs to
* @param {string} setting The name of the setting
* @param {any} defaultVal The value returned if the setting is not set
* @returns {any}
*/
export function getSetting (module, setting, defaultVal) {
const storageKey = `Toolbox.${module}.${setting}`;
registerSetting(module, setting);
defaultVal = defaultVal !== undefined ? defaultVal : null;
let result;
if (TBsettingsObject[storageKey] === undefined) {
return defaultVal;
} else {
result = TBsettingsObject[storageKey];
// send back the default if, somehow, someone stored `null`
// NOTE: never, EVER store `null`!
if (
result === null
&& defaultVal !== null
) {
result = defaultVal;
}
// Again, being extra paranoid here but let's sanitize.
const sanitzedResult = purifyThing(result);
return sanitzedResult;
}
}
/**
* Returns the value of a setting.
* @param {string} module The ID of the module the setting belongs to
* @param {string} setting The name of the setting
* @param {any} defaultVal The value returned if the setting is not set
* @returns {Promise<any>}
*/
export async function getSettingAsync (module, setting, defaultVal) {
await initialLoadPromise;
return getSetting(module, setting, defaultVal);
}
/**
* Sets a setting to a new value.
* @deprecated Use `setSettingAsync` instead
* @param {string} module The ID of the module the setting belongs to
* @param {string} setting The name of the setting
* @param {any} value The new value of the setting
* @param {boolean} [syncSettings=true] If false, settings will not be committed
* to storage after performing this action
* @returns {any} The new value of the setting
*/
export function setSetting (module, setting, value, syncSettings = true) {
const storageKey = `Toolbox.${module}.${setting}`;
registerSetting(module, setting);
// Sanitize the setting
const sanitzedValue = purifyThing(value);
TBsettingsObject[storageKey] = sanitzedValue;
// try to save our settings.
if (syncSettings) {
saveSettingsToBrowser();
// Communicate the new setting to other open tabs.
browser.runtime.sendMessage({
action: 'tb-global',
globalEvent: 'tb-single-setting-update',
excludeBackground: true,
payload: {
key: storageKey,
value: sanitzedValue,
},
});
}
return getSetting(module, setting);
}
/**
* Sets a setting to a new value.
* @param {string} module The ID of the module the setting belongs to
* @param {string} setting The name of the setting
* @param {any} value The new value of the setting
* @param {boolean} [syncSettings=true] If false, settings will not be committed
* to storage after performing this action
* @returns {Promise<any>} The new value of the setting
*/
export async function setSettingAsync (module, setting, value, syncSettings = true) {
await initialLoadPromise;
return setSetting(module, setting, value, syncSettings);
}
/**
* Gets a value in the cache.
* @param {string} module The module that owns the cache key
* @param {string} setting The name of the cache key
* @param {any} [defaultVal] The value returned if there is no cached value
* @returns {Promise<any>}
*/
export function getCache (module, setting, defaultVal) {
return new Promise(resolve => {
const storageKey = `${module}.${setting}`;
const inputValue = defaultVal !== undefined ? defaultVal : null;
browser.runtime.sendMessage({
action: 'tb-cache',
method: 'get',
storageKey,
inputValue,
}).then(response => {
if (response.errorThrown !== undefined) {
logger.debug(`${storageKey} is corrupted. Sending default.`);
resolve(defaultVal);
} else {
resolve(response.value);
}
});
});
}
/**
* Sets a value in the cache.
* @param {string} module The ID of the module that owns the cache key
* @param {string} setting The name of the cache key
* @param {any} inputValue The new value of the cache key
* @returns {Promise<any>} Promises the new value of the cache key
*/
export function setCache (module, setting, inputValue) {
const storageKey = `${module}.${setting}`;
return new Promise(resolve => {
browser.runtime.sendMessage({
action: 'tb-cache',
method: 'set',
storageKey,
inputValue,
}).then(async () => {
const value = await getCache(module, setting);
resolve(value);
});
});
}
/**
* Checks whether two objects are deeply equivalent by recursively comparing
* their property values. Does not check reference equality.
* @param {any} a
* @param {any} b
* @returns {boolean}
*/
// based on: http://designpepper.com/blog/drips/object-equality-in-javascript.html
// added recursive object checks - al
function isEquivalent (a, b) {
// Create arrays of property names
const aProps = Object.getOwnPropertyNames(a);
const bProps = Object.getOwnPropertyNames(b);
// If number of properties is different,
// objects are not equivalent
if (aProps.length !== bProps.length) {
logger.debug(`length :${aProps.length} ${bProps.length}`);
return false;
}
for (let i = 0; i < aProps.length; i++) {
const propName = aProps[i];
const propA = a[propName];
const propB = b[propName];
// If values of same property are not equal,
// objects are not equivalent
if (propA !== propB) {
if (typeof propA === 'object' && typeof propB === 'object') {
if (!isEquivalent(propA, propB)) {
logger.debug(`prop :${propA} ${propB}`);
return false;
}
} else {
logger.debug(`prop :${propA} ${propB}`);
return false;
}
}
}
// If we made it this far, objects
// are considered equivalent
return true;
}
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Thu Jan 23 2025 14:58:03 GMT+0000 (Coordinated Universal Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/polyfill.js"></script>
<script src="scripts/linenumber.js"></script>
<script src="scripts/search.js" defer></script>
</body>
</html>