diff --git a/README.md b/README.md index 89ddb41d..0a1f9fab 100644 --- a/README.md +++ b/README.md @@ -41,3 +41,16 @@ Next start Grist with an URL pointing to a local widget manifest file: ```bash GRIST_WIDGET_LIST_URL=http://localhost:8585/manifest.json npm start ``` + +Alternatively you can run the widget repository development server alongside with the Grist docker image preconfigured to use it: + +```bash +yarn run grist:serve +``` + +or run it in development mode with automatic reload: + +```bash +yarn install +yarn run grist:dev +``` \ No newline at end of file diff --git a/custom-widget-builder/README.md b/custom-widget-builder/README.md new file mode 100644 index 00000000..1332e185 --- /dev/null +++ b/custom-widget-builder/README.md @@ -0,0 +1,38 @@ +# Grist Custom Widget + +This widget enables you to create other custom widgets for Grist documents, right inside Grist that are hosted by Grist itself. + +## Getting Started + +To begin developing your custom widget, follow these steps: + +1. **Open the Widget Editor:** Click on the "Open configuration" button in the creator panel or clear the saved filter settings for the relevant tab. +2. **Edit Code:** Write your widget's logic in the JavaScript tab and structure its appearance in the HTML tab. +3. **Preview and Install:** Click the "Preview" button to see your widget in action. This will save the widget's code to the document's metadata. +4. **Save Configuration:** Press the "Save" button to persist the widget settings to ensure they remain active after refreshing the page. + +**Note:** There is no autosave feature, so always remember to save your configuration manually. + +## Data Storage + +The widget's configuration data is stored in the widget's metadata using the following format: + +```javascript +const options = { + _installed: true, + _js: `...your JavaScript code...`, + _html: `...your HTML code...`, +}; +grist.setOptions(options); +``` + +In the final widget, the _html field is inserted as is into an iframe, and the _js field is embedded within a script tag afterwards. + +This widget in itself doesn't require any access to documents metadata, but it can be used to create widgets that do. Storing Javascript and HTML code in the metadata stores it only temporarily. User needs to save it in order to persist the changes (just like for regular filters). + +Any contribution is welcome, the big thing missing is dark mode support. + + +## IntelliSense + +The widget editor supports `IntelliSense` for the `JavaScript` code. It does it by providing its own types definitions directly to the Monaco editor. The IntelliSense is based on the official Grist Plugin API. See the `genarate.js` script for more details. \ No newline at end of file diff --git a/custom-widget-builder/api.js b/custom-widget-builder/api.js new file mode 100644 index 00000000..71e05f71 --- /dev/null +++ b/custom-widget-builder/api.js @@ -0,0 +1,404 @@ +/** Helper for keeping some data and watching for changes */ +function memory(name) { + let value = undefined; + let listeners = []; + const obj = function (arg) { + if (arg === undefined) { + return value; + } else { + if (value !== arg) { + listeners.forEach(clb => clb(arg)); + value = arg; + } + } + }; + + obj.subscribe = function (clb) { + listeners.push(clb); + return () => void listeners.splice(listeners.indexOf(clb), 1); + }; + + return obj; +} + +// Global state, to keep track of the editor state, and file content. +const currentJs = memory('js'); +const currentHtml = memory('html'); +const state = memory('state'); // null, 'installed', 'editor' + +const COLORS = { + green: '#16b378', +} + +const DEFAULT_HTML = ` + + + + +
+

+ Custom widget builder +

+

+ For instructions on how to use this widget, click the "Open configuration" button on the creator panel and select the "Help" tab. +

+

+ Remember: there is no autosaving! Always save changes before closing/refreshing the page. +

+
+ + +`.trim(); + +const DEFAULT_JS = ` +grist.ready({ requiredAccess: 'none' }); +grist.onRecords(table => { + +}); +grist.onRecord(record => { + +}); +`.trim(); + +let htmlModel; +let jsModel; + +let monacoLoaded = false; +async function loadMonaco() { + // Load all those scripts above. + + if (monacoLoaded) { + return; + } + + monacoLoaded = true; + + async function loadJs(url) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = url; + script.onload = resolve; + script.onerror = reject; + document.head.appendChild(script); + }); + } + async function loadCss(url) { + return new Promise((resolve, reject) => { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = url; + link.onload = resolve; + link.onerror = reject; + document.head.appendChild(link); + }); + } + + await loadCss( + 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.26.1/min/vs/editor/editor.main.min.css' + ); + await loadJs( + 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.26.1/min/vs/loader.min.js' + ); + + window.require.config({ + paths: { + vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.26.1/min/vs', + }, + }); + + await new Promise((resolve, reject) => { + window.require( + ['vs/editor/editor.main.nls', 'vs/editor/editor.main'], + resolve, + reject + ); + }); +} + +// Builds code editor replacing all ' + * + * + * Example usage (let's assume that Grist let's plugin contributes to a Foo API defined as follow ): + * + * interface Foo { + * foo(name: string): Promise; + * } + * + * > main.ts: + * class MyFoo { + * public foo(name: string): Promise { + * return new Promise( async resolve => { + * grist.rpc.onMessage( e => { + * resolve(e.data + name); + * }); + * grist.ready(); + * await grist.api.render('view1.html', 'fullscreen'); + * }); + * } + * } + * grist.rpc.registerImpl('grist', new MyFoo()); // can add 3rd arg with type information + * + * > view1.html includes: + * grist.api.render('static/view2.html', 'fullscreen').then( view => { + * grist.rpc.onMessage(e => grist.rpc.postMessageForward("main.ts", e.data)); + * }); + * + * > view2.html includes: + * grist.rpc.postMessage('view1.html', 'foo '); + * + */ + import { RenderOptions, RenderTarget } from 'grist/RenderOptions'; + /** + * Represents the id of a row in a table. The value of the 'id' column. Might be a number or 'new' value for a new row. + */ + export type UIRowId = number | 'new'; + /** + * Represents the position of an active cursor on a page. + */ + export interface CursorPos { + /** + * The rowId (value of the 'id' column) of the current cursor position, or 'new' if the cursor is on a new row. + */ + rowId?: UIRowId; + /** + * The index of the current row in the current view. + */ + rowIndex?: number; + /** + * The index of the selected field in the current view. + */ + fieldIndex?: number; + /** + * The id of a section that this cursor is in. Ignored when setting a cursor position for a particular view. + */ + sectionId?: number; + /** + * When in a linked section, CursorPos may include which rows in the controlling sections are + * selected: the rowId in the linking-source section, in _that_ section's linking source, etc. + */ + linkingRowIds?: UIRowId[]; + } + export type ComponentKind = "safeBrowser" | "safePython" | "unsafeNode"; + export const RPC_GRISTAPI_INTERFACE = "_grist_api"; + export interface GristAPI { + /** + * Render the file at 'path' into the 'target' location in Grist. 'path' must be relative to the + * root of the plugin's directory and point to an html that is contained within the plugin's + * directory. 'target' is a predefined location of the Grist UI, it could be 'fullscreen' or + * identifier for an inline target. Grist provides inline target identifiers in certain call + * plugins. E.g. ImportSourceAPI.getImportSource is given a target identifier to allow rende UI + * inline in the import dialog. Returns the procId which can be used to dispose the view. + */ + render(path: string, target: RenderTarget, options?: RenderOptions): Promise; + /** + * Dispose the process with id procId. If the process was embedded into the UI, removes the + * corresponding element from the view. + */ + dispose(procId: number): Promise; + subscribe(tableId: string): Promise; + unsubscribe(tableId: string): Promise; + } + /** + * Allows getting information from and interacting with the Grist document to which a plugin or widget is attached. + */ + export interface GristDocAPI { + /** + * Returns an identifier for the document. + */ + getDocName(): Promise; + /** + * Returns a sorted list of table IDs. + */ + listTables(): Promise; + /** + * Returns a complete table of data as {@link GristData.RowRecords | GristData.RowRecords}, including the + * 'id' column. Do not modify the returned arrays in-place, especially if used + * directly (not over RPC). + */ + fetchTable(tableId: string): Promise; + /** + * Applies an array of user actions. + */ + applyUserActions(actions: any[][], options?: any): Promise; + /** + * Get a token for out-of-band access to the document. + */ + getAccessToken(options: AccessTokenOptions): Promise; + } + /** + * Options for functions which fetch data from the selected table or record: + * + * - {@link onRecords} + * - {@link onRecord} + * - {@link fetchSelectedRecord} + * - {@link fetchSelectedTable} + * - {@link GristView.fetchSelectedRecord | GristView.fetchSelectedRecord} + * - {@link GristView.fetchSelectedTable | GristView.fetchSelectedTable} + * + * The different methods have different default values for 'keepEncoded' and 'format'. + **/ + export interface FetchSelectedOptions { + /** + * - 'true': the returned data will contain raw {@link GristData.CellValue}'s. + * - 'false': the values will be decoded, replacing e.g. '['D', timestamp]' with a moment date. + */ + keepEncoded?: boolean; + /** + * - 'rows', the returned data will be an array of objects, one per row, with column names as keys. + * - 'columns', the returned data will be an object with column names as keys, and arrays of values. + */ + format?: 'rows' | 'columns'; + /** + * - 'shown' (default): return only columns that are explicitly shown + * in the right panel configuration of the widget. This is the only value that doesn't require full access. + * - 'normal': return all 'normal' columns, regardless of whether the user has shown them. + * - 'all': also return special invisible columns like 'manualSort' and display helper columns. + */ + includeColumns?: 'shown' | 'normal' | 'all'; + } + /** + * Interface for the data backing a single widget. + */ + export interface GristView { + /** + * Like {@link GristDocAPI.fetchTable | GristDocAPI.fetchTable}, + * but gets data for the custom section specifically, if there is any. + * By default, 'options.keepEncoded' is 'true' and 'format' is 'columns'. + */ + fetchSelectedTable(options?: FetchSelectedOptions): Promise; + /** + * Fetches selected record by its 'rowId'. By default, 'options.keepEncoded' is 'true'. + */ + fetchSelectedRecord(rowId: number, options?: FetchSelectedOptions): Promise; + /** + * Deprecated now. It was used for filtering selected table by 'setSelectedRows' method. + * Now the preferred way it to use ready message. + */ + allowSelectBy(): Promise; + /** + * Set the list of selected rows to be used against any linked widget. + */ + setSelectedRows(rowIds: number[] | null): Promise; + /** + * Sets the cursor position to a specific row and field. 'sectionId' is ignored. Used for widget linking. + */ + setCursorPos(pos: CursorPos): Promise; + } + /** + * Options when creating access tokens. + */ + export interface AccessTokenOptions { + /** Restrict use of token to reading only */ + readOnly?: boolean; + } + /** + * Access token information, including the token string itself, a base URL for + * API calls for which the access token can be used, and the time-to-live the + * token was created with. + */ + export interface AccessTokenResult { + /** + * The token string, which can currently be provided in an api call as a + * query parameter called "auth" + */ + token: string; + /** + * The base url of the API for which the token can be used. Currently tokens + * are associated with a single document, so the base url will be something + * like 'https://..../api/docs/DOCID' + * + * Access tokens currently only grant access to endpoints dealing with the + * internal content of a document (such as tables and cells) and not its + * metadata (such as the document name or who it is shared with). + */ + baseUrl: string; + /** + * Number of milliseconds the access token will remain valid for + * after creation. This will be several minutes. + */ + ttlMsecs: number; + } +} + +declare module 'grist/GristData' { + /** + * Letter codes for {@link CellValue} types encoded as [code, args...] tuples. + */ + export enum GristObjCode { + List = "L", + LookUp = "l", + Dict = "O", + DateTime = "D", + Date = "d", + Skip = "S", + Censored = "C", + Reference = "R", + ReferenceList = "r", + Exception = "E", + Pending = "P", + Unmarshallable = "U", + Versions = "V" + } + /** + * Possible types of cell content. + * + * Each 'CellValue' may either be a primitive (e.g. 'true', '123', '"hello"', 'null') + * or a tuple (JavaScript Array) representing a Grist object. The first element of the tuple + * is a string character representing the object code. For example, '["L", "foo", "bar"]' + * is a 'CellValue' of a Choice List column, where '"L"' is the type, and '"foo"' and + * '"bar"' are the choices. + * + * ### Grist Object Types + * + * | Code | Type | + * | ---- | -------------- | + * | 'L' | List, e.g. '["L", "foo", "bar"]' or '["L", 1, 2]' | + * | 'l' | LookUp, as '["l", value, options]' | + * | 'O' | Dict, as '["O", {key: value, ...}]' | + * | 'D' | DateTimes, as '["D", timestamp, timezone]', e.g. '["D", 1704945919, "UTC"]' | + * | 'd' | Date, as '["d", timestamp]', e.g. '["d", 1704844800]' | + * | 'C' | Censored, as '["C"]' | + * | 'R' | Reference, as '["R", table_id, row_id]', e.g. '["R", "People", 17]' | + * | 'r' | ReferenceList, as '["r", table_id, row_id_list]', e.g. '["r", "People", [1,2]]' | + * | 'E' | Exception, as '["E", name, ...]', e.g. '["E", "ValueError"]' | + * | 'P' | Pending, as '["P"]' | + * | 'U' | Unmarshallable, as '["U", text_representation]' | + * | 'V' | Version, as '["V", version_obj]' | + */ + export type CellValue = number | string | boolean | null | [GristObjCode, ...unknown[]]; + export interface BulkColValues { + [colId: string]: CellValue[]; + } + /** + * Map of column ids to {@link CellValue}'s. + */ + export interface RowRecord { + id: number; + [colId: string]: CellValue; + } + /** + * Map of column ids to {@link CellValue} arrays, where array indexes correspond to + * rows. + */ + export interface RowRecords { + id: number[]; + [colId: string]: CellValue[]; + } + export type GristType = 'Any' | 'Attachments' | 'Blob' | 'Bool' | 'Choice' | 'ChoiceList' | 'Date' | 'DateTime' | 'Id' | 'Int' | 'ManualSortPos' | 'Numeric' | 'PositionNumber' | 'Ref' | 'RefList' | 'Text'; +} + +declare module 'grist/RenderOptions' { + /** + * Where to append the content that a plugin renders. + * + * @internal + */ + export type RenderTarget = "fullscreen" | number; + /** + * Options for the 'grist.render' function. + */ + export interface RenderOptions { + height?: string; + } +} + +declare module 'grist/TableOperations' { + import * as Types from 'grist/DocApiTypes'; + /** + * Offer CRUD-style operations on a table. + */ + export interface TableOperations { + /** + * Create a record or records. + */ + create(records: Types.NewRecord, options?: OpOptions): Promise; + create(records: Types.NewRecord[], options?: OpOptions): Promise; + /** + * Update a record or records. + */ + update(records: Types.Record | Types.Record[], options?: OpOptions): Promise; + /** + * Delete a record or records. + */ + destroy(recordIds: Types.RecordId | Types.RecordId[]): Promise; + /** + * Add or update a record or records. + */ + upsert(records: Types.AddOrUpdateRecord | Types.AddOrUpdateRecord[], options?: UpsertOptions): Promise; + /** + * Determine the tableId of the table. + */ + getTableId(): Promise; + } + /** + * General options for table operations. + */ + export interface OpOptions { + /** Whether to parse strings based on the column type. Defaults to true. */ + parseStrings?: boolean; + } + /** + * Extra options for upserts. + */ + export interface UpsertOptions extends OpOptions { + /** Permit inserting a record. Defaults to true. */ + add?: boolean; + /** Permit updating a record. Defaults to true. */ + update?: boolean; + /** Whether to update none, one, or all matching records. Defaults to "first". */ + onMany?: 'none' | 'first' | 'all'; + /** Allow "wildcard" operation. Defaults to false. */ + allowEmptyRequire?: boolean; + } +} + +declare module 'grist/WidgetAPI' { + /** + * API to manage Custom Widget state. + */ + export interface WidgetAPI { + /** + * Gets all options stored by the widget. Options are stored as plain JSON object. + */ + getOptions(): Promise; + /** + * Replaces all options stored by the widget. + */ + setOptions(options: { + [key: string]: any; + }): Promise; + /** + * Clears all the options. + */ + clearOptions(): Promise; + /** + * Store single value in the Widget options object (and create it if necessary). + */ + setOption(key: string, value: any): Promise; + /** + * Get single value from Widget options object. + */ + getOption(key: string): Promise; + } +} + +declare module 'grist/TypeCheckers' { + import { ICheckerSuite } from 'ts-interface-checker'; + import CustomSectionAPITI from 'grist/CustomSectionAPI-ti'; + import FileParserAPITI from 'grist/FileParserAPI-ti'; + import GristAPITI from 'grist/GristAPI-ti'; + import GristTableTI from 'grist/GristTable-ti'; + import ImportSourceAPITI from 'grist/ImportSourceAPI-ti'; + import InternalImportSourceAPITI from 'grist/InternalImportSourceAPI-ti'; + import RenderOptionsTI from 'grist/RenderOptions-ti'; + import StorageAPITI from 'grist/StorageAPI-ti'; + import WidgetAPITI from 'grist/WidgetAPI-ti'; + /** + * The ts-interface-checker type suites are all exported with the "TI" suffix. + */ + export { CustomSectionAPITI, FileParserAPITI, GristAPITI, GristTableTI, ImportSourceAPITI, InternalImportSourceAPITI, RenderOptionsTI, StorageAPITI, WidgetAPITI }; + /** + * We also create and export a global checker object that includes all of the types above. + */ + export const checkers: Pick; +} + +declare module 'grist/FileParserAPI' { + /** + * API definitions for FileParser plugins. + */ + import { GristTables } from 'grist/GristTable'; + export interface EditOptionsAPI { + getParseOptions(parseOptions?: ParseOptions): Promise; + } + export interface ParseFileAPI { + parseFile(file: FileSource, parseOptions?: ParseOptions): Promise; + } + /** + * ParseOptions contains parse options depending on plugin, + * number of rows, which is special option that can be used for any plugin + * and schema for generating parse options UI + */ + export interface ParseOptions { + NUM_ROWS?: number; + SCHEMA?: ParseOptionSchema[]; + WARNING?: string; + } + /** + * ParseOptionSchema contains information for generaing parse options UI + */ + export interface ParseOptionSchema { + name: string; + label: string; + type: string; + visible: boolean; + } + export interface FileSource { + /** + * The path is often a temporary file, so its name is meaningless. Access to the file depends on + * the type of plugin. For instance, for 'safePython' plugins file is directly available at + * '/importDir/path'. + */ + path: string; + /** + * Plugins that want to know the original filename should use origName. Depending on the source + * of the data, it may or may not be meaningful. + */ + origName: string; + } + export interface ParseFileResult extends GristTables { + parseOptions: ParseOptions; + } +} + +declare module 'grist/GristTable' { + /** + * Metadata and data for a table. + */ + export interface GristTable { + table_name: string | null; + column_metadata: GristColumn[]; + table_data: any[][]; + } + export interface GristTables { + tables: GristTable[]; + } + /** + * Metadata about a single column. + */ + export interface GristColumn { + id: string; + type: string; + } + export enum APIType { + ImportSourceAPI = 0, + ImportProcessorAPI = 1, + ParseOptionsAPI = 2, + ParseFileAPI = 3 + } +} + +declare module 'grist/ImportSourceAPI' { + /** + * API definitions for ImportSource plugins. + */ + import { GristTable } from 'grist/GristTable'; + export interface ImportSourceAPI { + /** + * Returns a promise that resolves to an 'ImportSource' which is then passed for import to the + * import modal dialog. 'undefined' interrupts the workflow and prevent the modal from showing up, + * but not an empty list of 'ImportSourceItem'. Which is a valid import source and is used in + * cases where only options are to be sent to an 'ImportProcessAPI' implementation. + */ + getImportSource(): Promise; + } + export interface ImportProcessorAPI { + processImport(source: ImportSource): Promise; + } + export interface FileContent { + content: any; + name: string; + } + export interface FileListItem { + kind: "fileList"; + files: FileContent[]; + } + export interface URL { + kind: "url"; + url: string; + } + export interface ImportSource { + item: FileListItem | URL; + /** + * The options are only passed within this plugin, nothing else needs to know how they are + * serialized. Using JSON.stringify/JSON.parse is a simple approach. + */ + options?: string | Buffer; + /** + * The short description that shows in the import dialog after source have been selected. + */ + description?: string; + } +} + +declare module 'grist/StorageAPI' { + export interface Storage { + getItem(key: string): any; + hasItem(key: string): boolean; + setItem(key: string, value: any): void; + removeItem(key: string): void; + clear(): void; + } +} + +declare module 'grist/DocApiTypes' { + import { CellValue } from "grist/GristData"; + /** + * JSON schema for api /record endpoint. Used in POST method for adding new records. + */ + export interface NewRecord { + /** + * Initial values of cells in record. Optional, if not set cells are left + * blank. + */ + fields?: { + [coldId: string]: CellValue; + }; + } + export interface NewRecordWithStringId { + id?: string; + /** + * Initial values of cells in record. Optional, if not set cells are left + * blank. + */ + fields?: { + [coldId: string]: CellValue; + }; + } + /** + * JSON schema for api /record endpoint. Used in PATCH method for updating existing records. + */ + export interface Record { + id: number; + fields: { + [coldId: string]: CellValue; + }; + } + export interface RecordWithStringId { + id: string; + fields: { + [coldId: string]: CellValue; + }; + } + /** + * JSON schema for api /record endpoint. Used in PUT method for adding or updating records. + */ + export interface AddOrUpdateRecord { + /** + * The values we expect to have in particular columns, either by matching with + * an existing record, or creating a new record. + */ + require: { + [coldId: string]: CellValue; + } & { + id?: number; + }; + /** + * The values we will place in particular columns, either overwriting values in + * an existing record, or setting initial values in a new record. + */ + fields?: { + [coldId: string]: CellValue; + }; + } + /** + * JSON schema for the body of api /record PATCH endpoint + */ + export interface RecordsPatch { + records: [Record, ...Record[]]; + } + /** + * JSON schema for the body of api /record POST endpoint + */ + export interface RecordsPost { + records: [NewRecord, ...NewRecord[]]; + } + /** + * JSON schema for the body of api /record PUT endpoint + */ + export interface RecordsPut { + records: [AddOrUpdateRecord, ...AddOrUpdateRecord[]]; + } + export type RecordId = number; + /** + * The row id of a record, without any of its content. + */ + export interface MinimalRecord { + id: number; + } + export interface ColumnsPost { + columns: [NewRecordWithStringId, ...NewRecordWithStringId[]]; + } + export interface ColumnsPatch { + columns: [RecordWithStringId, ...RecordWithStringId[]]; + } + export interface ColumnsPut { + columns: [RecordWithStringId, ...RecordWithStringId[]]; + } + /** + * Creating tables requires a list of columns. + * 'fields' is not accepted because it's not generally sensible to set the metadata fields on new tables. + */ + export interface TablePost extends ColumnsPost { + id?: string; + } + export interface TablesPost { + tables: [TablePost, ...TablePost[]]; + } + export interface TablesPatch { + tables: [RecordWithStringId, ...RecordWithStringId[]]; + } + /** + * JSON schema for the body of api /sql POST endpoint + */ + export interface SqlPost { + sql: string; + args?: any[]; + timeout?: number; + } +} + +declare module 'grist/CustomSectionAPI-ti' { + /** + * This module was automatically generated by 'ts-interface-builder' + */ + import * as t from "ts-interface-checker"; + export const ColumnToMap: t.TIface; + export const ColumnsToMap: t.TArray; + export const InteractionOptionsRequest: t.TIface; + export const InteractionOptions: t.TIface; + export const WidgetColumnMap: t.TIface; + export const CustomSectionAPI: t.TIface; + const exportedTypeSuite: t.ITypeSuite; + export default exportedTypeSuite; +} + +declare module 'grist/FileParserAPI-ti' { + /** + * This module was automatically generated by 'ts-interface-builder' + */ + import * as t from "ts-interface-checker"; + export const EditOptionsAPI: t.TIface; + export const ParseFileAPI: t.TIface; + export const ParseOptions: t.TIface; + export const ParseOptionSchema: t.TIface; + export const FileSource: t.TIface; + export const ParseFileResult: t.TIface; + const exportedTypeSuite: t.ITypeSuite; + export default exportedTypeSuite; +} + +declare module 'grist/GristAPI-ti' { + /** + * This module was automatically generated by 'ts-interface-builder' + */ + import * as t from "ts-interface-checker"; + export const UIRowId: t.TUnion; + export const CursorPos: t.TIface; + export const ComponentKind: t.TUnion; + export const GristAPI: t.TIface; + export const GristDocAPI: t.TIface; + export const FetchSelectedOptions: t.TIface; + export const GristView: t.TIface; + export const AccessTokenOptions: t.TIface; + export const AccessTokenResult: t.TIface; + const exportedTypeSuite: t.ITypeSuite; + export default exportedTypeSuite; +} + +declare module 'grist/GristTable-ti' { + /** + * This module was automatically generated by 'ts-interface-builder' + */ + import * as t from "ts-interface-checker"; + export const GristTable: t.TIface; + export const GristTables: t.TIface; + export const GristColumn: t.TIface; + export const APIType: t.TEnumType; + const exportedTypeSuite: t.ITypeSuite; + export default exportedTypeSuite; +} + +declare module 'grist/ImportSourceAPI-ti' { + /** + * This module was automatically generated by 'ts-interface-builder' + */ + import * as t from "ts-interface-checker"; + export const ImportSourceAPI: t.TIface; + export const ImportProcessorAPI: t.TIface; + export const FileContent: t.TIface; + export const FileListItem: t.TIface; + export const URL: t.TIface; + export const ImportSource: t.TIface; + const exportedTypeSuite: t.ITypeSuite; + export default exportedTypeSuite; +} + +declare module 'grist/InternalImportSourceAPI-ti' { + /** + * This module was automatically generated by 'ts-interface-builder' + */ + import * as t from "ts-interface-checker"; + export const InternalImportSourceAPI: t.TIface; + const exportedTypeSuite: t.ITypeSuite; + export default exportedTypeSuite; +} + +declare module 'grist/RenderOptions-ti' { + /** + * This module was automatically generated by 'ts-interface-builder' + */ + import * as t from "ts-interface-checker"; + export const RenderTarget: t.TUnion; + export const RenderOptions: t.TIface; + const exportedTypeSuite: t.ITypeSuite; + export default exportedTypeSuite; +} + +declare module 'grist/StorageAPI-ti' { + /** + * This module was automatically generated by 'ts-interface-builder' + */ + import * as t from "ts-interface-checker"; + export const Storage: t.TIface; + const exportedTypeSuite: t.ITypeSuite; + export default exportedTypeSuite; +} + +declare module 'grist/WidgetAPI-ti' { + /** + * This module was automatically generated by 'ts-interface-builder' + */ + import * as t from "ts-interface-checker"; + export const WidgetAPI: t.TIface; + const exportedTypeSuite: t.ITypeSuite; + export default exportedTypeSuite; +} + + +// Generated by dts-bundle v0.7.3 +// Dependencies for this module: +// ../../../../../grist-widget/js-fiddle/events +// ../../../../../grist-widget/js-fiddle/ts-interface-checker + +declare module 'grain-rpc' { + export * from 'grain-rpc/message'; + export * from 'grain-rpc/rpc'; +} + +declare module 'grain-rpc/message' { + /** + * This defines the message types sent over an RpcChannel. + * + * WARNING: Any changes to these must be backward-compatible, since Rpc may be used across + * different versions of this library. Specifically, enums must not be renumbered, fields renamed, + * or their types changed. Really, the only reasonable enhancement is adding a new optional field. + */ + export enum MsgType { + RpcCall = 1, + RpcRespData = 2, + RpcRespErr = 3, + Custom = 4, + Ready = 5, + } + export interface IMsgRpcCall { + mtype: MsgType.RpcCall; + mdest?: string; + reqId?: number; + iface: string; + meth: string; + args: any[]; + } + export interface IMsgRpcRespData { + mtype: MsgType.RpcRespData; + reqId: number; + data?: any; + } + export interface IMsgRpcRespErr { + mtype: MsgType.RpcRespErr; + reqId: number; + mesg: string; + code?: string; + } + export interface IMsgCustom { + mtype: MsgType.Custom; + mdest?: string; + data: any; + } + export interface IMsgReady { + mtype: MsgType.Ready; + } + export type IMsgRpc = IMsgRpcCall | IMsgRpcRespData | IMsgRpcRespErr; + export type IMessage = IMsgRpc | IMsgCustom | IMsgReady; +} + +declare module 'grain-rpc/rpc' { + /** + * Rpc implements an remote-procedure-call interface on top of a simple messaging interface. + * + * The user must provide the messaging between two endpoints, and in return gets the ability to + * register interfaces or functions at either endpoint, and call them from the other side. For + * messaging, the user must supply a sendMessage() function to send messages to the other side, + * and must call rpc.receiveMessage(msg) whenever a message is received. + * + * E.g. + * rpc.registerImpl("some-name", new MyInterfaceImpl(), descMyInterfaceImpl); + * rpc.getStub("some-name", descMyInterfaceImpl) + * => returns a stub implemeting MyInterface + * + * Calls to the generated stub get turned into messages across the channel, and then call to the + * implementation object registered on the other side. Both return values and exceptions get + * passed back over the channel, and cause the promise from the stub to be resolved or rejected. + * + * Note that the stub interface returns Promises for all methods. + * + * Rpc library supports ts-interface-checker descriptors for the interfaces, to allow validation. + * You may skip it by passing in 'rpc.unchecked' where a descriptor is expected; it will skip + * checks and you will not get descriptive errors. + * + * The string name used to register and use an implementation allows for the same Rpc object to be + * used to expose multiple interfaces, or different implementations of the same interface. + * + * Messaging + * --------- + * Rpc also supports a messaging interface, with postMessage() to send arbitrary messages, and an + * EventEmitter interface for "message" events to receive them, e.g. on("message", ...). So if you + * need to multiplex non-Rpc messages over the same channel, Rpc class does it for you. + * + * Cleanup + * ------- + * If the channel is closed or had an error, and will no longer be used, the user of Rpc must + * call rpc.close() to reject any calls waiting for an answer. + * + * If a particular stub for a remote API is no longer needed, user may call rpc.discardStub(stub) + * to reject any pending calls made to that stub. + * + * Timeouts + * -------- + * TODO (Not yet implementd.) + * You may call rpc.setTimeout(ms) or rpc.setStubTimeout(stub, ms) to set a call timeout for all + * stubs or for a particular one. If a response to a call does not arrive within the timeout, the + * call gets rejected, and the rejection Error will have a "code" property set to "TIMEOUT". + * + * Forwarding + * ---------- + * Rpc.registerForwarder() along with methods with "-Forward" suffix allow one Rpc object to forward + * calls and messages to another Rpc object. The intended usage is when Rpc connects A to B, and B + * to C. Then B can use registerForwarder to expose A's interfaces to C (or C's to A) without having + * to know what exactly they are. A default forwarder can be registered using the '*' name. + * + * + * Instead of using getStubForward and callRemoteFuncForward, the forwarder name can be + * appended to the interface name as "interfaceName@forwarderName" and the regular + * getStub and callRemoteFunc methods can be used. For example: + * getStub("iface@forwarder") + * is the same as: + * getStubForward("forwarder", "iface") + * + * + * E.g. with A.registerImpl("A-name", ...) and B.registerForwarder("b2a", A), we may now call + * C.getStubForward("b2a", "A-name") to get a stub that will forward calls to A, as well as + * C.postMessageForward("b2a", msg) to have the message received by A. + * + * TODO We may want to support progress callbacks, perhaps by supporting arbitrary callbacks as + * parameters. (Could be implemented by allowing "meth" to be [reqId, paramPath]) It would be nice + * to allow the channel to report progress too, e.g. to report progress of uploading large files. + * + * TODO Sending of large files should probably be a separate feature, to allow for channel + * implementations to stream them. + */ + import { EventEmitter } from "events"; + import * as tic from "ts-interface-checker"; + import { IMessage, IMsgCustom, IMsgRpcCall } from "grain-rpc/message"; + export type SendMessageCB = (msg: IMessage) => Promise | void; + export interface IForwarderDest { + forwardCall: (c: IMsgRpcCall) => Promise; + forwardMessage: (msg: IMsgCustom) => Promise; + } + export type ICallWrapper = (callFunc: () => Promise) => Promise; + export class Rpc extends EventEmitter implements IForwarderDest { + /** + * To use Rpc, you must provide a sendMessage function that sends a message to the other side; + * it may be given in the constructor, or later with setSendMessage. You must also call + * receiveMessage() for every message received from the other side. + */ + constructor(options?: { + logger?: IRpcLogger; + sendMessage?: SendMessageCB; + callWrapper?: ICallWrapper; + }); + /** + * To use Rpc, call this for every message received from the other side of the channel. + */ + receiveMessage(msg: IMessage): void; + /** + * If you've set up calls to receiveMessage(), but need time to call registerImpl() before + * processing new messages, you may use queueIncoming(), make the registerImpl() calls, + * and then call processIncoming() to handle queued messages and resume normal processing. + */ + queueIncoming(): void; + /** + * Process received messages queued since queueIncoming, and resume normal processing of + * received messages. + */ + processIncoming(): void; + /** + * Set the callback to send messages. If set to null, sent messages will be queued. If you + * disconnect and want for sent messages to throw, set a callback that throws. + */ + setSendMessage(sendMessage: SendMessageCB | null): void; + /** + * If your peer may not be listening yet to your messages, you may call this to queue outgoing + * messages until receiving a "ready" message from the peer. I.e. one peer may call + * queueOutgoingUntilReadyMessage() while the other calls sendReadyMessage(). + */ + queueOutgoingUntilReadyMessage(): void; + /** + * If your peer is using queueOutgoingUntilReadyMessage(), you should let it know that you are + * ready using sendReadyMessage() as soon as you've set up the processing of received messages. + * Note that at most one peer may use queueOutgoingUntilReadyMessage(), or they will deadlock. + */ + sendReadyMessage(): void | Promise; + /** + * Messaging interface: send data to the other side, to be emitted there as a "message" event. + */ + postMessage(data: any): Promise; + postMessageForward(fwdDest: string, data: any): Promise; + /** + * Registers a new implementation under the given name. It is an error if this name is already + * in use. To skip all validation, use 'registerImpl(...)' and omit the last argument. + * TODO Check that registerImpl without a type param requires a checker. + */ + registerImpl(name: string, impl: any): void; + registerImpl(name: string, impl: Iface, checker: tic.Checker): void; + registerForwarder(fwdName: string, dest: IForwarderDest, fwdDest?: string): void; + unregisterForwarder(fwdName: string): void; + /** + * Unregister an implementation, if one was registered with this name. + */ + unregisterImpl(name: string): void; + /** + * Creates a local stub for the given remote interface. The stub implements Iface, forwarding + * calls to the remote implementation, each one returning a Promise for the received result. + * To skip all validation, use 'any' for the type and omit the last argument. + * + * Interface names can be followed by a "@" part + */ + getStub(name: string): Iface; + getStub(name: string, checker: tic.Checker): Iface; + getStubForward(fwdDest: string, name: string): any; + getStubForward(fwdDest: string, name: string, checker: tic.Checker): Iface; + /** + * Simple way to registers a function under a given name, with no argument checking. + */ + registerFunc(name: string, impl: (...args: any[]) => any): void; + /** + * Unregister a function, if one was registered with this name. + */ + unregisterFunc(name: string): void; + /** + * Call a remote function registered with registerFunc. Does no type checking. + */ + callRemoteFunc(name: string, ...args: any[]): Promise; + callRemoteFuncForward(fwdDest: string, name: string, ...args: any[]): Promise; + forwardCall(c: IMsgRpcCall): Promise; + forwardMessage(msg: IMsgCustom): Promise; + } + /** + * Interfaces may throw errors that include .code field, and it gets propagated to callers (e.g. + * "NOT_AUTHORIZED"). Its purpose is to be a stable way to distinguish different types of errors. + * This way the human-friendly error message can be changed without affecting behavior. + */ + export class ErrorWithCode extends Error { + code: string | undefined; + constructor(code: string | undefined, message: string); + } + /** + * Rpc logs everything to the passed-in logger, which is by default the console, but you may + * provide your own. + */ + export interface IRpcLogger { + info?(message: string): void; + warn?(message: string): void; + } +} + + +`; \ No newline at end of file diff --git a/custom-widget-builder/generate.js b/custom-widget-builder/generate.js new file mode 100644 index 00000000..185a5bf2 --- /dev/null +++ b/custom-widget-builder/generate.js @@ -0,0 +1,56 @@ +// Usage: +// node generate.js +// node generate.js ~/path/to/grist +// +// By default script will look for grist in ~/dev/grist +// To use globally installed dts-bundle version use: +// env NODE_PATH=$(npm root --quiet -g) node generate.js + + +const dts = require('dts-bundle'); +const path = require('path'); +const fs = require('fs'); +const homedir = require('os').homedir(); + +// REPLACE THIS PATH WITH YOUR PATH TO YOUR GRIST FOLDER or pass as argument to either core or enterprise version +// You need to built Grist first. +const GRIST_PATH = process.argv[2] || path.resolve(homedir, 'dev/grist'); + +// Detect Grist version (EE or Core) +const eePath = path.resolve(GRIST_PATH, '_build/core/app/plugin/grist-plugin-api.d.ts'); +const corePath = path.resolve(GRIST_PATH, '_build/app/plugin/grist-plugin-api.d.ts'); +const pluginPath = fs.existsSync(eePath) ? eePath : corePath; + +console.log(`Using ${pluginPath === eePath ? 'GristEE' : 'GristCore'} located in ${pluginPath}`) + +// Bundle grist-plugin.api.d.ts and grain-rpc.d.ts to two files. +const pluginOptions = { + main: pluginPath, + name: 'grist', + out: path.resolve(__dirname, 'grist.d.ts'), +}; +const rpcOptions = { + main: path.resolve(GRIST_PATH, 'node_modules/grain-rpc/dist/lib/index.d.ts'), + name: 'grain-rpc', + out: path.resolve(__dirname, 'grains-rpc.d.ts'), +}; +dts.bundle(pluginOptions); +dts.bundle(rpcOptions); + +// Get bundle contents, and regenerate api_deps.js by combining those source +// codes into a single string variable. +const contents = [pluginOptions.out, rpcOptions.out].map(f => fs.readFileSync(f).toString()).join("\n") + .replace(/`/g, "'") + .replace(/\$/g, '\\$'); +const apiDepsFile = path.resolve(__dirname, "api_deps.js"); +const apiDepsContent = ` +// THIS FILE WAS AUTOGENERATED BY ./generate.js +const definition = \` +${contents} +\`; +`.trim(); +fs.writeFileSync(apiDepsFile, apiDepsContent); + +// Remove intermediate files +fs.unlinkSync(pluginOptions.out); +fs.unlinkSync(rpcOptions.out); diff --git a/custom-widget-builder/index.html b/custom-widget-builder/index.html new file mode 100644 index 00000000..a8a43e16 --- /dev/null +++ b/custom-widget-builder/index.html @@ -0,0 +1,167 @@ + + + + + Grist Fiddle + + + + + + + + +
+ +
+ + + + + \ No newline at end of file diff --git a/custom-widget-builder/package.json b/custom-widget-builder/package.json new file mode 100644 index 00000000..33ddc994 --- /dev/null +++ b/custom-widget-builder/package.json @@ -0,0 +1,24 @@ +{ + "name": "@berhalak/custom-widget-builder", + "description": "Editor for building widgets with HTML and JS inside Grist.", + "homePage": "https://github.com/gristlabs/grist-widget/tree/master/custom-widget-builder", + "version": "0.0.1", + "grist": [ + { + "name": "Custom widget builder", + "url": "https://gristlabs.github.io/grist-widget/custom-widget-builder/index.html", + "widgetId": "@berhalak/custom-widget-builder", + "published": true, + "accessLevel": "none", + "renderAfterReady": true, + "description": "Build custom widgets with HTML and JavaScript, right inside Grist.", + "isGristLabsMaintained": false, + "authors": [ + { + "name": "berhalak", + "url": "https://github.com/berhalak" + } + ] + } + ] +} diff --git a/package.json b/package.json index f4717729..5c949d1a 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,18 @@ "description": "A repository of grist custom widgets that have no back-end requirements.", "scripts": { "build": "tsc --build && node ./buildtools/publish.js manifest.json", - "serve": "live-server --port=8585 --no-browser -q", + "serve": "http-server -c-1 --port=8585 -s", "build:dev": "node ./buildtools/publish.js manifest.json http://localhost:8585", "serve:dev": "live-server --port=8585 --no-browser -q --middleware=$(pwd)/buildtools/rewriteUrl.js", "watch": "nodemon --ignore manifest.json -e js,json --exec 'npm run build:dev'", - "dev": "echo 'Starting local server and watching for changes.\nStart Grist with an environmental variable GRIST_WIDGET_LIST_URL=http://localhost:8585/manifest.json' && npm run watch 1> /dev/null & npm run serve:dev 1> /dev/null", + "dev": "echo 'Starting local server and watching for changes.\nStart Grist with an environmental variable GRIST_WIDGET_LIST_URL=http://localhost:8585/manifest.json' && npm run watch 1> /dev/null & npm run serve:dev 1> /dev/null && fg", "test": "docker image inspect gristlabs/grist --format 'gristlabs/grist image present' && NODE_PATH=_build SELENIUM_BROWSER=chrome mocha -g \"${GREP_TESTS}\" _build/test/*.js", "test:ci": "MOCHA_WEBDRIVER_HEADLESS=1 npm run test", "pretest": "docker pull gristlabs/grist && tsc --build && node ./buildtools/publish.js manifest.json http://localhost:9998", - "prepack": "node ./buildtools/bundle.js --name grist-bundled --unlisted" + "prepack": "node ./buildtools/bundle.js --name grist-bundled --unlisted", + "grist": "docker run --add-host host.docker.internal:host-gateway --rm --env PORT=8484 --env GRIST_WIDGET_LIST_URL=http://host.docker.internal:8585/manifest.json -p 8484:8484 -v $PWD/_build/persist:/persist -it gristlabs/grist", + "grist:dev": "npm run dev & npm run grist", + "grist:serve": "yarn && npm run build:dev && npm run serve & npm run grist" }, "devDependencies": { "@types/chai": "^4.3.5", @@ -32,7 +35,9 @@ "ts-node": "^10.9.1", "typescript": "^5.1.3" }, - "dependencies": {}, + "dependencies": { + "http-server": "^14.1.1" + }, "mocha": { "require": [ "_build/test/init-mocha-webdriver" diff --git a/yarn.lock b/yarn.lock index 3c9e4684..80ff1ba9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -233,6 +233,13 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== +async@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -261,7 +268,7 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -basic-auth@~2.0.1: +basic-auth@^2.0.1, basic-auth@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== @@ -380,6 +387,17 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + camelcase@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" @@ -410,7 +428,7 @@ chai@^4.1.2: pathval "^1.1.1" type-detect "^4.0.5" -chalk@^4.1.0: +chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -595,6 +613,11 @@ cors@latest: object-assign "^4" vary "^1" +corser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87" + integrity sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ== + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -660,6 +683,15 @@ defer-to-connect@^1.0.1: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" @@ -751,6 +783,18 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -789,6 +833,11 @@ event-stream@3.3.4: stream-combiner "~0.0.4" through "~2.3.1" +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -886,6 +935,11 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +follow-redirects@^1.0.0: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -944,6 +998,11 @@ fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -954,6 +1013,17 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -1019,6 +1089,13 @@ global-dirs@^3.0.0: dependencies: ini "2.0.0" +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -1056,6 +1133,23 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -1092,11 +1186,25 @@ has-yarn@^2.1.0: resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== -he@1.2.0: +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +he@1.2.0, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + http-auth@3.1.x: version "3.1.3" resolved "https://registry.yarnpkg.com/http-auth/-/http-auth-3.1.3.tgz#945cfadd66521eaf8f7c84913d377d7b15f24e31" @@ -1138,6 +1246,41 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.5.tgz#d7c30d5d3c90d865b4a2e870181f9d6f22ac7ac5" integrity sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA== +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http-server@^14.1.1: + version "14.1.1" + resolved "https://registry.yarnpkg.com/http-server/-/http-server-14.1.1.tgz#d60fbb37d7c2fdff0f0fbff0d0ee6670bd285e2e" + integrity sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A== + dependencies: + basic-auth "^2.0.1" + chalk "^4.1.2" + corser "^2.0.1" + he "^1.2.0" + html-encoding-sniffer "^3.0.0" + http-proxy "^1.18.1" + mime "^1.6.0" + minimist "^1.2.6" + opener "^1.5.1" + portfinder "^1.0.28" + secure-compare "3.0.1" + union "~0.5.0" + url-join "^4.0.1" + +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" @@ -1488,7 +1631,7 @@ lodash.escaperegexp@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw== -lodash@^4.17.21: +lodash@^4.17.14, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -1597,7 +1740,7 @@ mime-types@~2.1.17, mime-types@~2.1.24: dependencies: mime-db "1.51.0" -mime@1.6.0: +mime@1.6.0, mime@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -1633,6 +1776,11 @@ minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -1641,6 +1789,13 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp@^0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + mocha-webdriver@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/mocha-webdriver/-/mocha-webdriver-0.3.1.tgz#5ed238e710ee2e4dfe72208cdd1dc4a15d2aa644" @@ -1811,6 +1966,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -1844,6 +2004,11 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +opener@^1.5.1: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + opn@latest: version "6.0.0" resolved "https://registry.yarnpkg.com/opn/-/opn-6.0.0.tgz#3c5b0db676d5f97da1233d1ed42d182bc5a27d2d" @@ -1932,6 +2097,15 @@ picomatch@^2.0.4, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== +portfinder@^1.0.28: + version "1.0.32" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81" + integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg== + dependencies: + async "^2.6.4" + debug "^3.2.7" + mkdirp "^0.5.6" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -1972,6 +2146,13 @@ pupa@^2.1.1: dependencies: escape-goat "^2.0.0" +qs@^6.4.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -2078,6 +2259,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -2119,6 +2305,16 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +secure-compare@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" + integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw== + selenium-webdriver@^4.11.1: version "4.14.0" resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.14.0.tgz#d39917cd7c1bb30f753c1f668158f37d1905fafc" @@ -2191,6 +2387,18 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -2216,6 +2424,16 @@ setprototypeof@1.1.1: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + signal-exit@^3.0.2: version "3.0.6" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" @@ -2482,6 +2700,13 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" +union@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075" + integrity sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA== + dependencies: + qs "^6.4.0" + unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -2542,6 +2767,11 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url-join@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + url-parse-lax@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" @@ -2598,6 +2828,13 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"