Skip to content

Commit

Permalink
Drop .define (#21)
Browse files Browse the repository at this point in the history
Definitions are now part of the constructor
  • Loading branch information
fregante committed Jun 23, 2019
1 parent 6a2f414 commit a5b4316
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 108 deletions.
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,18 @@
"devDependencies": {
"@sindresorhus/tsconfig": "^0.4.0",
"@types/chrome": "0.0.86",
"@types/dom-inputevent": "^1.0.4",
"@types/dom-inputevent": "^1.0.5",
"@typescript-eslint/eslint-plugin": "^1.9.0",
"@typescript-eslint/parser": "^1.10.2",
"browser-pack-flat": "^3.4.2",
"browserify": "^16.2.3",
"eslint-config-xo-typescript": "^0.14.0",
"npm-run-all": "^4.1.5",
"tsify": "^4.0.1",
"typescript": "^3.5.1",
"typescript": "^3.5.2",
"xo": "*"
},
"dependencies": {
"webext-detect-page": "^0.9.1"
}
}
67 changes: 39 additions & 28 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Create your options definition file, for example `options-init.js`:

```js
/* globals OptionsSync */
new OptionsSync().define({
new OptionsSync({
defaults: {
yourStringOption: 'green',
anyBooleans: true,
Expand Down Expand Up @@ -111,15 +111,15 @@ new OptionsSync().syncForm(document.querySelector('form#options-form'));

Done. Any defaults or saved options will be loaded into the form and any change will automatically be saved via `chrome.storage.sync`

In alternative you can put your fields in a custom`<options-sync>` element instead of `<form>` and they'll be automatically synchronized. You can specify the `storageName` via attribute, like:
In alternative you can put your fields in a custom`<options-sync>` element instead of `<form>` and they'll be automatically synchronized. You can specify the `storageName` via attribute, like:

```html
<options-sync storageName="my-options">
<input type="color" name="color">
</options-sync>
```

<strong>Warning:</strong> Custom Elements are supported by Firefox 63+ (November 2018)
<strong>Warning:</strong> Custom Elements are only supported by Firefox 63+ (November 2018)

</details>

Expand Down Expand Up @@ -154,7 +154,7 @@ In your `options-init.js` file, extend the call by including an array of functio

```js
/* globals OptionsSync */
new OptionsSync().define({
new OptionsSync({
defaults: {
color: 'red',
},
Expand All @@ -177,39 +177,19 @@ Notice `OptionsSync.migrations.removeUnused`: it's a helper method that removes

## API

#### const opts = new OptionsSync([options])

Returns an instance linked to the chosen storage.

##### storageName

Type: `string`
Default: `'options'`

The key used to store data in `chrome.storage.sync`

##### logging

Type: `boolean`
Default: `true`

Determines whether info and warnings (on sync, updating form, etc.) should be logged to the console or not. Recommended when used in content scripts.

#### opts.define(setup)

To be used in the background only, this is used to initiate the options. It's not required but it's recommended as a way to define which options the extension supports.
#### const optionsStorage = new OptionsSync([setup])

##### setup

Type: `object`

It should follow this format:
Optional. It should follow this format:

```js
{
defaults: { // recommended
color: 'blue'
},
},
migrations: [ // optional
savedOptions => {
if(savedOptions.oldStuff) {
Expand All @@ -220,6 +200,8 @@ It should follow this format:
}
```

Returns an instance linked to the chosen storage.

###### defaults

Type: `object`
Expand All @@ -230,7 +212,36 @@ A map of default options as strings or booleans. The keys will have to match the

Type: `array`

A list of functions to call when the extension is updated. The function will have this signature: `(savedOptions, defaults)`. In this function, alter the `savedOptions`. Don't return anything.
A list of functions to run in the `background` when the extension is updated. Example:

```js
{
migrations: [
(savedOptions, defaults) => {
// Change the `savedOptions`
if(savedOptions.oldStuff) {
delete savedOptions.oldStuff
}

// No return needed
}
],
}
```

###### storageName

Type: `string`
Default: `'options'`

The key used to store data in `chrome.storage.sync`

###### logging

Type: `boolean`
Default: `true`

Whether info and warnings (on sync, updating form, etc.) should be logged to the console or not.

#### opts.getAll()

Expand Down
79 changes: 34 additions & 45 deletions source/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import {isBackgroundPage} from 'webext-detect-page';

declare namespace OptionsSync {
interface ModuleOptions {
interface Settings<TOptions extends Options> {
storageName?: string;
logging?: boolean;
defaults?: TOptions;
/**
* A list of functions to call when the extension is updated.
*/
migrations?: Array<Migration<TOptions>>;
}

/**
A map of options as strings or booleans. The keys will have to match the form fields' `name` attributes.
*/
type Options = Record<string, string | number | boolean>;
interface Options {
[key: string]: string | number | boolean;
}

/*
Handler signature for when an extension updates.
*/
type Migration = (savedOptions: Options, defaults: Options) => void;
type Migration<TOptions extends Options> = (savedOptions: TOptions, defaults: TOptions) => void;

/**
@example
Expand All @@ -32,17 +41,9 @@ declare namespace OptionsSync {
],
}
*/

interface Definitions {
defaults: Options;
/**
* A list of functions to call when the extension is updated.
*/
migrations: Migration[];
}
}

class OptionsSync {
class OptionsSync<TOptions extends OptionsSync.Options> {
public static migrations = {
/**
Helper method that removes any option that isn't defined in the defaults. It's useful to avoid leaving old options taking up space.
Expand All @@ -58,22 +59,33 @@ class OptionsSync {

storageName: string;

defaults: OptionsSync.Options;

private _timer?: NodeJS.Timeout;

/**
@constructor Returns an instance linked to the chosen storage.
@param options - Configuration to determine where options are stored.
*/
constructor(options: OptionsSync.ModuleOptions = {}) {
if (typeof options === 'string') {
options = {
storageName: options
};
constructor(options: OptionsSync.Settings<TOptions>) {
const fullOptions: Required<OptionsSync.Settings<TOptions>> = {
storageName: 'options',
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
defaults: {} as TOptions, // https://github.com/bfred-it/webext-options-sync/pull/21#issuecomment-500314074
migrations: [],
logging: true,
...options
};

this.storageName = fullOptions.storageName;
this.defaults = fullOptions.defaults;

if (fullOptions.logging === false) {
this._log = () => {};
}

this.storageName = options.storageName || 'options';
if (options.logging === false) {
this._log = () => {};
if (isBackgroundPage()) {
chrome.runtime.onInstalled.addListener(() => this._applyDefinition(fullOptions));
}

this._handleFormUpdatesDebounced = this._handleFormUpdatesDebounced.bind(this);
Expand All @@ -83,35 +95,12 @@ class OptionsSync {
console[method](...args);
}

/**
To be used in the background only. This is used to initiate the options. It's not required but it's recommended as a way to define which options the extension supports.
@example
new OptionsSync().define({
defaults: {
yourStringOption: 'green',
anyBooleans: true,
numbersAreFine: 9001
}
});
*/
define(defs: OptionsSync.Definitions): void {
defs = {defaults: {},
migrations: [], ...defs};

if (chrome.runtime.onInstalled) { // In background script
chrome.runtime.onInstalled.addListener(() => this._applyDefinition(defs));
} else { // In content script, discouraged
this._applyDefinition(defs);
}
}

async _applyDefinition(defs: OptionsSync.Definitions): Promise<void> {
async _applyDefinition(defs: Required<OptionsSync.Settings<TOptions>>): Promise<void> {
const options = {...defs.defaults, ...await this.getAll()};

this._log('group', 'Appling definitions');
this._log('info', 'Current options:', options);
if (defs.migrations.length > 0) {
if (defs.migrations && defs.migrations.length > 0) {
this._log('info', 'Running', defs.migrations.length, 'migrations');
defs.migrations.forEach(migrate => migrate(options, defs.defaults));
}
Expand Down
81 changes: 48 additions & 33 deletions webext-options-sync.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,69 @@
// https://github.com/bfred-it/webext-options-sync

(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.OptionsSync = f()}})(function(){var define,module,exports;
var _$webextDetectPage_1 = {};
"use strict";
// https://github.com/bfred-it/webext-detect-page
Object.defineProperty(_$webextDetectPage_1, "__esModule", { value: true });
function isBackgroundPage() {
return location.pathname === '/_generated_background_page.html' &&
!location.protocol.startsWith('http') &&
Boolean(typeof chrome === 'object' && chrome.runtime);
}
_$webextDetectPage_1.isBackgroundPage = isBackgroundPage;
function isContentScript() {
return location.protocol.startsWith('http') &&
Boolean(typeof chrome === 'object' && chrome.runtime);
}
_$webextDetectPage_1.isContentScript = isContentScript;
function isOptionsPage() {
if (typeof chrome !== 'object' || !chrome.runtime) {
return false;
}
const { options_ui } = chrome.runtime.getManifest();
if (typeof options_ui !== 'object' || typeof options_ui.page !== 'string') {
return false;
}
const url = new URL(options_ui.page, location.origin);
return url.pathname === location.pathname &&
url.origin === location.origin;
}
_$webextDetectPage_1.isOptionsPage = isOptionsPage;
//# sourceMappingURL=index.js.map
"use strict";
/* removed: const _$webextDetectPage_1 = require("webext-detect-page"); */;
class OptionsSync {
/**
@constructor Returns an instance linked to the chosen storage.
@param options - Configuration to determine where options are stored.
*/
constructor(options = {}) {
if (typeof options === 'string') {
options = {
storageName: options
};
}
this.storageName = options.storageName || 'options';
if (options.logging === false) {
constructor(options) {
const fullOptions = {
storageName: 'options',
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
defaults: {},
migrations: [],
logging: true,
...options
};
this.storageName = fullOptions.storageName;
this.defaults = fullOptions.defaults;
if (fullOptions.logging === false) {
this._log = () => { };
}
if (_$webextDetectPage_1.isBackgroundPage()) {
chrome.runtime.onInstalled.addListener(() => this._applyDefinition(fullOptions));
}
this._handleFormUpdatesDebounced = this._handleFormUpdatesDebounced.bind(this);
}
_log(method, ...args) {
console[method](...args);
}
/**
To be used in the background only. This is used to initiate the options. It's not required but it's recommended as a way to define which options the extension supports.
@example
new OptionsSync().define({
defaults: {
yourStringOption: 'green',
anyBooleans: true,
numbersAreFine: 9001
}
});
*/
define(defs) {
defs = { defaults: {},
migrations: [], ...defs };
if (chrome.runtime.onInstalled) { // In background script
chrome.runtime.onInstalled.addListener(() => this._applyDefinition(defs));
}
else { // In content script, discouraged
this._applyDefinition(defs);
}
}
async _applyDefinition(defs) {
const options = { ...defs.defaults, ...await this.getAll() };
this._log('group', 'Appling definitions');
this._log('info', 'Current options:', options);
if (defs.migrations.length > 0) {
if (defs.migrations && defs.migrations.length > 0) {
this._log('info', 'Running', defs.migrations.length, 'migrations');
defs.migrations.forEach(migrate => migrate(options, defs.defaults));
}
Expand Down Expand Up @@ -213,9 +228,9 @@ if (typeof HTMLElement !== 'undefined' && typeof customElements !== 'undefined')
}
customElements.define('options-sync', OptionsSyncElement);
}
var _$OptionsSync_1 = OptionsSync;
var _$OptionsSync_2 = OptionsSync;

return _$OptionsSync_1;
return _$OptionsSync_2;

});

0 comments on commit a5b4316

Please sign in to comment.