From 71fc12289b5ad092a69e74a577e8c400ac1b5b9c Mon Sep 17 00:00:00 2001 From: kumavis Date: Sat, 2 Dec 2023 08:39:05 -1000 Subject: [PATCH 1/4] feat(familiar-chat): create familiar-chat package from endo pet daemon cli demo --- packages/familiar-chat/CHANGELOG.md | 28 +++ packages/familiar-chat/LICENSE | 201 ++++++++++++++++ packages/familiar-chat/NEWS.md | 0 packages/familiar-chat/README.md | 28 +++ packages/familiar-chat/SECURITY.md | 38 +++ packages/familiar-chat/index.js | 1 + packages/familiar-chat/package.json | 72 ++++++ packages/familiar-chat/src/index.js | 219 ++++++++++++++++++ .../test/prepare-test-env-ava.js | 9 + packages/familiar-chat/test/test-index.js | 5 + packages/familiar-chat/tsconfig.build.json | 10 + packages/familiar-chat/tsconfig.json | 4 + 12 files changed, 615 insertions(+) create mode 100644 packages/familiar-chat/CHANGELOG.md create mode 100644 packages/familiar-chat/LICENSE create mode 100644 packages/familiar-chat/NEWS.md create mode 100644 packages/familiar-chat/README.md create mode 100644 packages/familiar-chat/SECURITY.md create mode 100644 packages/familiar-chat/index.js create mode 100644 packages/familiar-chat/package.json create mode 100644 packages/familiar-chat/src/index.js create mode 100644 packages/familiar-chat/test/prepare-test-env-ava.js create mode 100644 packages/familiar-chat/test/test-index.js create mode 100644 packages/familiar-chat/tsconfig.build.json create mode 100644 packages/familiar-chat/tsconfig.json diff --git a/packages/familiar-chat/CHANGELOG.md b/packages/familiar-chat/CHANGELOG.md new file mode 100644 index 0000000000..76a8209da9 --- /dev/null +++ b/packages/familiar-chat/CHANGELOG.md @@ -0,0 +1,28 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +### [0.1.3](https://github.com/endojs/endo/compare/@endo/skel@0.1.2...@endo/skel@0.1.3) (2023-09-12) + +**Note:** Version bump only for package @endo/skel + + + + + +### 0.1.2 (2023-08-07) + + +### Bug Fixes + +* Fix scaffold and transforms yarn pack ([42439e7](https://github.com/endojs/endo/commit/42439e7d452e839b9856eac0e852766c237219d0)) + + + +### 0.1.1 (2023-08-07) + + +### Bug Fixes + +* Fix scaffold and transforms yarn pack ([42439e7](https://github.com/endojs/endo/commit/42439e7d452e839b9856eac0e852766c237219d0)) diff --git a/packages/familiar-chat/LICENSE b/packages/familiar-chat/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/packages/familiar-chat/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/familiar-chat/NEWS.md b/packages/familiar-chat/NEWS.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/familiar-chat/README.md b/packages/familiar-chat/README.md new file mode 100644 index 0000000000..29f8ce76f1 --- /dev/null +++ b/packages/familiar-chat/README.md @@ -0,0 +1,28 @@ + +This is a demo weblet that demonstrates a permission management UI for the +pet daemon itself. + +This command will set up the cat page, create a URL, +and open it. + +```sh +endo open familiar-chat cat.js --powers HOST +``` + +Thereafter, + +```sh +endo open famiar-chat +``` + +To interact with the permission manager, you can mock requests from a fake +guest. + +```sh +endo eval 42 --name ft +endo request --as cat 'pet me' +``` + +At this point, the command will pause, waiting for a response. +In the Familiar Chat window, resolve the request with the pet name "ft" and +the request will exit with the number 42. diff --git a/packages/familiar-chat/SECURITY.md b/packages/familiar-chat/SECURITY.md new file mode 100644 index 0000000000..17272ea788 --- /dev/null +++ b/packages/familiar-chat/SECURITY.md @@ -0,0 +1,38 @@ +# Security Policy + +## Supported Versions + +The SES package and associated Endo packages are still undergoing development and security review, and all +users are encouraged to use the latest version available. Security fixes will +be made for the most recent branch only. + +## Coordinated Vulnerability Disclosure of Security Bugs + +SES stands for fearless cooperation, and strong security requires strong collaboration with security researchers. If you believe that you have found a security sensitive bug that should not be disclosed until a fix has been made available, we encourage you to report it. To report a bug in HardenedJS, you have several options that include: + +* Reporting the issue to the [Agoric HackerOne vulnerability rewards program](https://hackerone.com/agoric). + +* Sending an email to security at (@) agoric.com., encrypted or unencrypted. To encrypt, please use @Warner’s personal GPG key [A476E2E6 11880C98 5B3C3A39 0386E81B 11CAA07A](http://www.lothar.com/warner-gpg.html) . + +* Sending a message on Keybase to `@agoric_security`, or sharing code and other log files via Keybase’s encrypted file system. ((_keybase_private/agoric_security,$YOURNAME). + +* It is important to be able to provide steps that reproduce the issue and demonstrate its impact with a Proof of Concept example in an initial bug report. Before reporting a bug, a reporter may want to have another trusted individual reproduce the issue. + +* A bug reporter can expect acknowledgment of a potential vulnerability reported through [security@agoric.com](mailto:security@agoric.com) within one business day of submitting a report. If an acknowledgement of an issue is not received within this time frame, especially during a weekend or holiday period, please reach out again. Any issues reported to the HackerOne program will be acknowledged within the time frames posted on the program page. + * The bug triage team and Agoric code maintainers are primarily located in the San Francisco Bay Area with business hours in [Pacific Time](https://www.timeanddate.com/worldclock/usa/san-francisco) . + +* For the safety and security of those who depend on the code, bug reporters should avoid publicly sharing the details of a security bug on Twitter, Discord, Telegram, or in public Github issues during the coordination process. + +* Once a vulnerability report has been received and triaged: + * Agoric code maintainers will confirm whether it is valid, and will provide updates to the reporter on validity of the report. + * It may take up to 72 hours for an issue to be validated, especially if reported during holidays or on weekends. + +* When the Agoric team has verified an issue, remediation steps and patch release timeline information will be shared with the reporter. + * Complexity, severity, impact, and likelihood of exploitation are all vital factors that determine the amount of time required to remediate an issue and distribute a software patch. + * If an issue is Critical or High Severity, Agoric code maintainers will release a security advisory to notify impacted parties to prepare for an emergency patch. + * While the current industry standard for vulnerability coordination resolution is 90 days, Agoric code maintainers will strive to release a patch as quickly as possible. + +When a bug patch is included in a software release, the Agoric code maintainers will: + * Confirm the version and date of the software release with the reporter. + * Provide information about the security issue that the software release resolves. + * Credit the bug reporter for discovery by adding thanks in release notes, securing a CVE designation, or adding the researcher’s name to a Hall of Fame. diff --git a/packages/familiar-chat/index.js b/packages/familiar-chat/index.js new file mode 100644 index 0000000000..78a8410d3e --- /dev/null +++ b/packages/familiar-chat/index.js @@ -0,0 +1 @@ +export { make } from './src/index.js'; \ No newline at end of file diff --git a/packages/familiar-chat/package.json b/packages/familiar-chat/package.json new file mode 100644 index 0000000000..5cc499760a --- /dev/null +++ b/packages/familiar-chat/package.json @@ -0,0 +1,72 @@ +{ + "name": "@endo/familiar-chat", + "private": true, + "version": "0.0.0", + "description": null, + "keywords": [], + "author": "Endo contributors", + "license": "Apache-2.0", + "homepage": "https://github.com/endojs/endo/tree/master/packages/familiar-chat#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/endojs/endo.git" + }, + "bugs": { + "url": "https://github.com/endojs/endo/issues" + }, + "type": "module", + "main": "./index.js", + "module": "./index.js", + "exports": { + ".": "./index.js", + "./package.json": "./package.json" + }, + "scripts": { + "start": "endo open familiar-chat ./index.js --powers SELF", + "test": "ava", + "test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js", + "test:xs": "exit 0", + "build": "exit 0", + "clean": "git clean -f '*.d.ts*'", + "prepack": "tsc --build tsconfig.build.json", + "postpack": "yarn clean", + "lint-fix": "yarn lint:eslint --fix", + "lint-check": "yarn lint", + "lint": "yarn lint:types && yarn lint:eslint", + "lint:types": "tsc", + "lint:eslint": "eslint '**/*.js'" + }, + "devDependencies": { + "@endo/lockdown": "^0.1.32", + "@endo/ses-ava": "^0.2.44", + "ava": "^5.3.0", + "c8": "^7.14.0", + "tsd": "^0.28.1" + }, + "files": [ + "*.js", + "*.ts", + "LICENSE*", + "SECURITY*", + "src" + ], + "publishConfig": { + "access": "public" + }, + "eslintConfig": { + "extends": [ + "plugin:@endo/internal" + ] + }, + "ava": { + "files": [ + "test/**/test-*.js" + ], + "timeout": "2m" + }, + "dependencies": { + "@endo/cli": "^0.2.6", + "@endo/daemon": "^0.2.6", + "@endo/far": "^0.2.22" + } +} diff --git a/packages/familiar-chat/src/index.js b/packages/familiar-chat/src/index.js new file mode 100644 index 0000000000..0cbc29bd97 --- /dev/null +++ b/packages/familiar-chat/src/index.js @@ -0,0 +1,219 @@ +/* global window document */ + +import { E } from '@endo/far'; +import { makeRefIterator } from '@endo/daemon/ref-reader.js'; + +const dateFormatter = new window.Intl.DateTimeFormat(undefined, { + dateStyle: 'full', + timeStyle: 'long', +}); + +const followMessagesComponent = async ($parent, $end, powers) => { + for await (const message of makeRefIterator(E(powers).followMessages())) { + const { number, who, when, dismissed } = message; + + const $error = document.createElement('span'); + $error.style.color = 'red'; + $error.innerText = ''; + // To be inserted later, but declared here for reference. + + const $message = document.createElement('div'); + $parent.insertBefore($message, $end); + + dismissed.then(() => { + $message.remove(); + }); + + const $number = document.createElement('span'); + $number.innerText = `${number}. `; + $message.appendChild($number); + + const $who = document.createElement('b'); + $who.innerText = `${who}:`; + $message.appendChild($who); + + if (message.type === 'request') { + const { what, settled } = message; + + const $what = document.createElement('span'); + $what.innerText = ` ${what} `; + $message.appendChild($what); + + const $when = document.createElement('i'); + $when.innerText = dateFormatter.format(Date.parse(when)); + $message.appendChild($when); + + const $input = document.createElement('span'); + $message.appendChild($input); + + const $pet = document.createElement('input'); + $input.appendChild($pet); + + const $resolve = document.createElement('button'); + $resolve.innerText = 'resolve'; + $input.appendChild($resolve); + + const $reject = document.createElement('button'); + $reject.innerText = 'reject'; + $reject.onclick = () => { + E(powers).reject(number, $pet.value).catch(window.reportError); + }; + $input.appendChild($reject); + + $resolve.onclick = () => { + E(powers) + .resolve(number, $pet.value) + .catch(error => { + $error.innerText = ` ${error.message}`; + }); + }; + + settled.then(status => { + $input.innerText = ` ${status} `; + }); + } else if (message.type === 'package') { + const { strings, names } = message; + assert(Array.isArray(strings)); + assert(Array.isArray(names)); + + $message.appendChild(document.createTextNode(' "')); + + let index = 0; + for ( + index = 0; + index < Math.min(strings.length, names.length); + index += 1 + ) { + assert.typeof(strings[index], 'string'); + const outer = JSON.stringify(strings[index]); + const inner = outer.slice(1, outer.length - 1); + $message.appendChild(document.createTextNode(inner)); + assert.typeof(names[index], 'string'); + const name = `@${names[index]}`; + const $name = document.createElement('b'); + $name.innerText = name; + $message.appendChild($name); + } + if (strings.length > names.length) { + const outer = JSON.stringify(strings[index]); + const inner = outer.slice(1, outer.length - 1); + $message.appendChild(document.createTextNode(inner)); + } + + $message.appendChild(document.createTextNode('" ')); + + const $when = document.createElement('i'); + $when.innerText = dateFormatter.format(Date.parse(when)); + $message.appendChild($when); + + $message.appendChild(document.createTextNode(' ')); + + if (names.length > 0) { + const $names = document.createElement('select'); + $message.appendChild($names); + for (const name of names) { + const $name = document.createElement('option'); + $name.innerText = name; + $names.appendChild($name); + } + + $message.appendChild(document.createTextNode(' ')); + + const $as = document.createElement('input'); + $as.type = 'text'; + $message.appendChild($as); + + $message.appendChild(document.createTextNode(' ')); + + const $adopt = document.createElement('button'); + $adopt.innerText = 'Adopt'; + $message.appendChild($adopt); + $adopt.onclick = () => { + console.log($as.value, $as); + E(powers) + .adopt(number, $names.value, $as.value || $names.value) + .then( + () => { + $as.value = ''; + }, + error => { + $error.innerText = ` ${error.message}`; + }, + ); + }; + } + } + + $message.appendChild(document.createTextNode(' ')); + + const $dismiss = document.createElement('button'); + $dismiss.innerText = 'Dismiss'; + $message.appendChild($dismiss); + $dismiss.onclick = () => { + E(powers) + .dismiss(number) + .catch(error => { + $error.innerText = ` ${error.message}`; + }); + }; + + $message.appendChild($error); + } +}; + +const followNamesComponent = async ($parent, $end, powers) => { + const $title = document.createElement('h2'); + $title.innerText = 'Inventory'; + $parent.insertBefore($title, $end); + + const $ul = document.createElement('ul'); + $parent.insertBefore($ul, $end); + + const $names = new Map(); + for await (const change of makeRefIterator(E(powers).followNames())) { + if ('add' in change) { + const name = change.add; + + const $li = document.createElement('li'); + $ul.appendChild($li); + + const $name = document.createTextNode(`${name} `); + $li.appendChild($name); + $name.innerText = change.add; + + const $remove = document.createElement('button'); + $li.appendChild($remove); + $remove.innerText = 'Remove'; + $remove.onclick = () => E(powers).remove(name).catch(window.reportError); + + $names.set(name, $li); + } else if ('remove' in change) { + const $li = $names.get(change.remove); + if ($li !== undefined) { + $li.remove(); + $names.delete(change.remove); + } + } + } +}; + +const bodyComponent = ($parent, powers) => { + const $title = document.createElement('h1'); + $title.innerText = '🐈‍⬛'; + $parent.appendChild($title); + + const $endOfMessages = document.createTextNode(''); + $parent.appendChild($endOfMessages); + followMessagesComponent($parent, $endOfMessages, powers).catch( + window.reportError, + ); + + const $endOfNames = document.createTextNode(''); + $parent.appendChild($endOfNames); + followNamesComponent($parent, $endOfNames, powers).catch(window.reportError); +}; + +export const make = async powers => { + document.body.innerHTML = ''; + bodyComponent(document.body, powers); +}; diff --git a/packages/familiar-chat/test/prepare-test-env-ava.js b/packages/familiar-chat/test/prepare-test-env-ava.js new file mode 100644 index 0000000000..f5ed7c0c84 --- /dev/null +++ b/packages/familiar-chat/test/prepare-test-env-ava.js @@ -0,0 +1,9 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import '@endo/init/debug.js'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import { wrapTest } from '@endo/ses-ava'; +import rawTest from 'ava'; + +/** @type {typeof rawTest} */ +export const test = wrapTest(rawTest); diff --git a/packages/familiar-chat/test/test-index.js b/packages/familiar-chat/test/test-index.js new file mode 100644 index 0000000000..9da523a4be --- /dev/null +++ b/packages/familiar-chat/test/test-index.js @@ -0,0 +1,5 @@ +import { test } from './prepare-test-env-ava.js'; + +test('place holder', async t => { + t.pass(); +}); diff --git a/packages/familiar-chat/tsconfig.build.json b/packages/familiar-chat/tsconfig.build.json new file mode 100644 index 0000000000..77b3f63183 --- /dev/null +++ b/packages/familiar-chat/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "declarationMap": true + }, + "exclude": ["test/"] +} diff --git a/packages/familiar-chat/tsconfig.json b/packages/familiar-chat/tsconfig.json new file mode 100644 index 0000000000..9c0f0590f0 --- /dev/null +++ b/packages/familiar-chat/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../jsconfig.eslint-base.json", + "include": ["*.js", "*.ts", "src/**/*.js", "src/**/*.ts"] +} From 7cd8f62578e00243be899db3f1dc67b8cdc29bf7 Mon Sep 17 00:00:00 2001 From: kumavis Date: Sat, 2 Dec 2023 08:41:43 -1000 Subject: [PATCH 2/4] fix(familiar-chat): fix type issues --- packages/familiar-chat/src/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/familiar-chat/src/index.js b/packages/familiar-chat/src/index.js index 0cbc29bd97..4232a05e83 100644 --- a/packages/familiar-chat/src/index.js +++ b/packages/familiar-chat/src/index.js @@ -1,8 +1,11 @@ -/* global window document */ +/* global window document globalThis */ import { E } from '@endo/far'; import { makeRefIterator } from '@endo/daemon/ref-reader.js'; +/** @type any */ +const { assert } = globalThis; + const dateFormatter = new window.Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long', @@ -72,6 +75,7 @@ const followMessagesComponent = async ($parent, $end, powers) => { $input.innerText = ` ${status} `; }); } else if (message.type === 'package') { + /** @type {{ strings: Array, names: Array }} */ const { strings, names } = message; assert(Array.isArray(strings)); assert(Array.isArray(names)); @@ -179,7 +183,7 @@ const followNamesComponent = async ($parent, $end, powers) => { const $name = document.createTextNode(`${name} `); $li.appendChild($name); - $name.innerText = change.add; + $name.nodeValue = change.add; const $remove = document.createElement('button'); $li.appendChild($remove); From d6344fcbeb5a18c0754bb3df6a0349c922e39167 Mon Sep 17 00:00:00 2001 From: kumavis Date: Sat, 2 Dec 2023 12:06:00 -1000 Subject: [PATCH 3/4] fix(pass-style): allow error with line numbers --- packages/pass-style/src/error.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/pass-style/src/error.js b/packages/pass-style/src/error.js index c41e1871e6..74cb653b08 100644 --- a/packages/pass-style/src/error.js +++ b/packages/pass-style/src/error.js @@ -81,6 +81,12 @@ export const ErrorHelper = harden({ // TODO Must allow `cause`, `errors` message: mDesc, stack: stackDesc, + + // workaround + columnNumber: _cn, + fileName: _fn, + lineNumber: _ln, + ...restDescs } = getOwnPropertyDescriptors(candidate); ownKeys(restDescs).length < 1 || From 6dcc0fc1f6ed8438c411d8fe4ce98a4e198d0110 Mon Sep 17 00:00:00 2001 From: kumavis Date: Sat, 2 Dec 2023 12:51:46 -1000 Subject: [PATCH 4/4] feat(familiar-chat): rewrite ui as preact --- packages/familiar-chat/index.js | 2 +- packages/familiar-chat/package.json | 4 +- packages/familiar-chat/src/index.js | 451 ++++++++++++++++------------ patches/preact+10.19.2.patch | 8 + yarn.lock | 5 + 5 files changed, 281 insertions(+), 189 deletions(-) create mode 100644 patches/preact+10.19.2.patch diff --git a/packages/familiar-chat/index.js b/packages/familiar-chat/index.js index 78a8410d3e..5824291e5c 100644 --- a/packages/familiar-chat/index.js +++ b/packages/familiar-chat/index.js @@ -1 +1 @@ -export { make } from './src/index.js'; \ No newline at end of file +export { make } from './src/index.js'; diff --git a/packages/familiar-chat/package.json b/packages/familiar-chat/package.json index 5cc499760a..e433e27edf 100644 --- a/packages/familiar-chat/package.json +++ b/packages/familiar-chat/package.json @@ -23,6 +23,7 @@ }, "scripts": { "start": "endo open familiar-chat ./index.js --powers SELF", + "dev": "nodemon --exec 'endo open familiar-chat ./index.js --powers SELF'", "test": "ava", "test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js", "test:xs": "exit 0", @@ -67,6 +68,7 @@ "dependencies": { "@endo/cli": "^0.2.6", "@endo/daemon": "^0.2.6", - "@endo/far": "^0.2.22" + "@endo/far": "^0.2.22", + "preact": "^10.19.2" } } diff --git a/packages/familiar-chat/src/index.js b/packages/familiar-chat/src/index.js index 4232a05e83..56f7f7d986 100644 --- a/packages/familiar-chat/src/index.js +++ b/packages/familiar-chat/src/index.js @@ -2,6 +2,9 @@ import { E } from '@endo/far'; import { makeRefIterator } from '@endo/daemon/ref-reader.js'; +import 'preact/debug'; +import { h, render, Fragment } from 'preact'; +import { useState, useEffect } from 'preact/hooks'; /** @type any */ const { assert } = globalThis; @@ -11,213 +14,287 @@ const dateFormatter = new window.Intl.DateTimeFormat(undefined, { timeStyle: 'long', }); -const followMessagesComponent = async ($parent, $end, powers) => { - for await (const message of makeRefIterator(E(powers).followMessages())) { - const { number, who, when, dismissed } = message; - - const $error = document.createElement('span'); - $error.style.color = 'red'; - $error.innerText = ''; - // To be inserted later, but declared here for reference. - - const $message = document.createElement('div'); - $parent.insertBefore($message, $end); - - dismissed.then(() => { - $message.remove(); - }); - - const $number = document.createElement('span'); - $number.innerText = `${number}. `; - $message.appendChild($number); - - const $who = document.createElement('b'); - $who.innerText = `${who}:`; - $message.appendChild($who); - - if (message.type === 'request') { - const { what, settled } = message; - - const $what = document.createElement('span'); - $what.innerText = ` ${what} `; - $message.appendChild($what); - - const $when = document.createElement('i'); - $when.innerText = dateFormatter.format(Date.parse(when)); - $message.appendChild($when); - - const $input = document.createElement('span'); - $message.appendChild($input); - - const $pet = document.createElement('input'); - $input.appendChild($pet); +const arrayWithout = (array, value) => { + const newArray = array.slice(); + const index = newArray.indexOf(value); + if (index !== -1) { + newArray.splice(index, 1); + } + return newArray; +}; - const $resolve = document.createElement('button'); - $resolve.innerText = 'resolve'; - $input.appendChild($resolve); +const useAsync = (asyncFn, deps) => { + const [state, setState] = useState(); + useEffect(() => { + setState(undefined); + let shouldAbort = false; + const runAsync = async () => { + const result = await asyncFn(); + if (!shouldAbort) { + setState(result); + } + }; + runAsync(); + return () => { + shouldAbort = true; + }; + }, deps); + return state; +}; - const $reject = document.createElement('button'); - $reject.innerText = 'reject'; - $reject.onclick = () => { - E(powers).reject(number, $pet.value).catch(window.reportError); - }; - $input.appendChild($reject); +const useFollowReducer = (getSubFn, reducerFn, deps) => { + const [state, setState] = useState([]); + + useEffect(() => { + setState([]); + const sub = makeRefIterator(getSubFn()); + let shouldAbort = false; + const iterateChanges = async () => { + for await (const event of sub) { + // Check if we should abort iteration + if (shouldAbort) { + break; + } + reducerFn(event, setState); + } + }; + // start iteration + iterateChanges(); + // cleanup + return () => { + shouldAbort = true; + }; + }, deps); - $resolve.onclick = () => { - E(powers) - .resolve(number, $pet.value) - .catch(error => { - $error.innerText = ` ${error.message}`; - }); - }; + return state; +}; - settled.then(status => { - $input.innerText = ` ${status} `; +const useFollowMessages = (getSubFn, deps) => { + const reducerFn = (message, setState) => { + // apply change + setState(prevState => { + return [...prevState, message]; + }); + // listen for dismiss + message.dismissed.then(() => { + setState(prevState => { + return arrayWithout(prevState, message); }); - } else if (message.type === 'package') { - /** @type {{ strings: Array, names: Array }} */ - const { strings, names } = message; - assert(Array.isArray(strings)); - assert(Array.isArray(names)); - - $message.appendChild(document.createTextNode(' "')); - - let index = 0; - for ( - index = 0; - index < Math.min(strings.length, names.length); - index += 1 - ) { - assert.typeof(strings[index], 'string'); - const outer = JSON.stringify(strings[index]); - const inner = outer.slice(1, outer.length - 1); - $message.appendChild(document.createTextNode(inner)); - assert.typeof(names[index], 'string'); - const name = `@${names[index]}`; - const $name = document.createElement('b'); - $name.innerText = name; - $message.appendChild($name); - } - if (strings.length > names.length) { - const outer = JSON.stringify(strings[index]); - const inner = outer.slice(1, outer.length - 1); - $message.appendChild(document.createTextNode(inner)); - } - - $message.appendChild(document.createTextNode('" ')); + }); + }; - const $when = document.createElement('i'); - $when.innerText = dateFormatter.format(Date.parse(when)); - $message.appendChild($when); + const state = useFollowReducer(getSubFn, reducerFn, deps); + return state; +}; - $message.appendChild(document.createTextNode(' ')); +const useFollowNames = (getSubFn, deps) => { + const reducerFn = (change, setState) => { + // apply change + setState(prevState => { + if ('add' in change) { + const name = change.add; + return [...prevState, name]; + } else if ('remove' in change) { + const name = change.remove; + return arrayWithout(prevState, name); + } + return prevState; + }); + }; - if (names.length > 0) { - const $names = document.createElement('select'); - $message.appendChild($names); - for (const name of names) { - const $name = document.createElement('option'); - $name.innerText = name; - $names.appendChild($name); - } + const state = useFollowReducer(getSubFn, reducerFn, deps); + return state; +}; - $message.appendChild(document.createTextNode(' ')); - - const $as = document.createElement('input'); - $as.type = 'text'; - $message.appendChild($as); - - $message.appendChild(document.createTextNode(' ')); - - const $adopt = document.createElement('button'); - $adopt.innerText = 'Adopt'; - $message.appendChild($adopt); - $adopt.onclick = () => { - console.log($as.value, $as); - E(powers) - .adopt(number, $names.value, $as.value || $names.value) - .then( - () => { - $as.value = ''; - }, - error => { - $error.innerText = ` ${error.message}`; - }, - ); - }; - } +const packageMessageComponent = ({ message, actions }) => { + const { when, strings, names } = message; + assert(Array.isArray(strings)); + assert(Array.isArray(names)); + + const [asValue, setAsValue] = useState(''); + const [selectedName, setSelectedName] = useState(names[0]); + + const stringEntries = strings.map((string, index) => { + const name = names[index]; + if (name === undefined) { + // Special case for when there are more strings than names. + const textDisplay = JSON.stringify(string).slice(1, -1); + return textDisplay; + } else { + return h(Fragment, null, [string, h('b', null, `@${name}`)]); } + }); + + return h(Fragment, null, [ + ' "', + ...stringEntries, + '" ', + h('i', null, dateFormatter.format(Date.parse(when))), + ' ', + h( + 'select', + { + value: selectedName, + onchange: e => { + console.log(e.target.value); + setSelectedName(e.target.value); + }, + }, + names.map(name => h('option', { value: name }, name)), + ), + ' ', + h('input', { + type: 'text', + value: asValue, + oninput: e => setAsValue(e.target.value), + }), + h( + // @ts-ignore + 'button', + { + onclick: () => actions.adopt(selectedName, asValue), + }, + 'Adopt', + ), + ]); +}; - $message.appendChild(document.createTextNode(' ')); - - const $dismiss = document.createElement('button'); - $dismiss.innerText = 'Dismiss'; - $message.appendChild($dismiss); - $dismiss.onclick = () => { - E(powers) - .dismiss(number) - .catch(error => { - $error.innerText = ` ${error.message}`; - }); - }; - - $message.appendChild($error); - } +const requestMessageComponent = ({ message, actions }) => { + const [petName, setPetName] = useState(''); + const { what, when, settled } = message; + const status = useAsync(() => settled, [settled]); + const isUnsettled = status === undefined; + const statusText = isUnsettled ? '' : ` ${status} `; + + const makeControls = () => { + return h(Fragment, null, [ + h('input', { + type: 'text', + value: petName, + oninput: e => setPetName(e.target.value), + }), + h( + // @ts-ignore + 'button', + { + onclick: () => actions.resolve(petName), + }, + 'resolve', + ), + h( + // @ts-ignore + 'button', + { + onclick: () => actions.reject(petName), + }, + 'reject', + ), + ]); + }; + + return h(Fragment, null, [ + h('span', null, ` ${what} `), + h('i', null, dateFormatter.format(Date.parse(when))), + h('span', null, statusText), + isUnsettled && makeControls(), + ]); }; -const followNamesComponent = async ($parent, $end, powers) => { - const $title = document.createElement('h2'); - $title.innerText = 'Inventory'; - $parent.insertBefore($title, $end); - - const $ul = document.createElement('ul'); - $parent.insertBefore($ul, $end); - - const $names = new Map(); - for await (const change of makeRefIterator(E(powers).followNames())) { - if ('add' in change) { - const name = change.add; - - const $li = document.createElement('li'); - $ul.appendChild($li); - - const $name = document.createTextNode(`${name} `); - $li.appendChild($name); - $name.nodeValue = change.add; - - const $remove = document.createElement('button'); - $li.appendChild($remove); - $remove.innerText = 'Remove'; - $remove.onclick = () => E(powers).remove(name).catch(window.reportError); - - $names.set(name, $li); - } else if ('remove' in change) { - const $li = $names.get(change.remove); - if ($li !== undefined) { - $li.remove(); - $names.delete(change.remove); - } - } +const messageComponent = ({ message, powers }) => { + const { number, who } = message; + const [errorText, setErrorText] = useState(''); + + let messageBodyComponent; + if (message.type === 'request') { + messageBodyComponent = requestMessageComponent; + } else if (message.type === 'package') { + messageBodyComponent = packageMessageComponent; + } else { + throw new Error(`Unknown message type: ${message.type}`); } + + const reportError = error => { + setErrorText(error.message); + }; + const actions = { + dismiss: () => E(powers).dismiss(number).catch(reportError), + resolve: value => E(powers).resolve(number, value).catch(reportError), + reject: value => E(powers).reject(number, value).catch(reportError), + adopt: (selectedName, asValue) => + E(powers).adopt(number, selectedName, asValue).catch(reportError), + }; + + return h('div', null, [ + h('span', null, `${number}. `), + h('b', null, `${who}:`), + h(messageBodyComponent, { message, actions }), + ' ', + h( + // @ts-ignore + 'button', + { + onclick: () => actions.dismiss(), + }, + 'Dismiss', + ), + h( + 'span', + { + style: { + color: 'red', + }, + }, + errorText, + ), + ]); }; -const bodyComponent = ($parent, powers) => { - const $title = document.createElement('h1'); - $title.innerText = '🐈‍⬛'; - $parent.appendChild($title); +const followMessagesComponent = ({ powers }) => { + const messages = useFollowMessages(() => E(powers).followMessages(), []); + + const messageEntries = messages.map(message => { + return h(messageComponent, { message, powers }); + }); - const $endOfMessages = document.createTextNode(''); - $parent.appendChild($endOfMessages); - followMessagesComponent($parent, $endOfMessages, powers).catch( - window.reportError, - ); + return h(Fragment, null, [ + h('h2', null, 'Messages'), + h('div', null, messageEntries), + ]); +}; + +const followNamesComponent = ({ powers }) => { + const names = useFollowNames(() => E(powers).followNames(), []); + + const inventoryEntries = names.map(name => { + return h('li', null, [ + name, + h( + // @ts-ignore + 'button', + { + onclick: () => E(powers).remove(name).catch(window.reportError), + }, + 'Remove', + ), + ]); + }); + + return h(Fragment, null, [ + h('h2', null, 'Inventory'), + h('ul', null, inventoryEntries), + ]); +}; - const $endOfNames = document.createTextNode(''); - $parent.appendChild($endOfNames); - followNamesComponent($parent, $endOfNames, powers).catch(window.reportError); +const bodyComponent = ({ powers }) => { + return h(Fragment, null, [ + h('h1', {}, '🐈‍⬛'), + h(followMessagesComponent, { powers }), + h(followNamesComponent, { powers }), + ]); }; export const make = async powers => { document.body.innerHTML = ''; - bodyComponent(document.body, powers); + const app = h(bodyComponent, { powers }); + render(app, document.body); }; diff --git a/patches/preact+10.19.2.patch b/patches/preact+10.19.2.patch new file mode 100644 index 0000000000..f6d533f5e9 --- /dev/null +++ b/patches/preact+10.19.2.patch @@ -0,0 +1,8 @@ +diff --git a/node_modules/preact/dist/preact.mjs b/node_modules/preact/dist/preact.mjs +index e3ae6a4..e6d1797 100644 +--- a/node_modules/preact/dist/preact.mjs ++++ b/node_modules/preact/dist/preact.mjs +@@ -1,2 +1,2 @@ +-var n,l,u,t,i,o,r,f,e,c={},s=[],a=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,h=Array.isArray;function v(n,l){for(var u in l)n[u]=l[u];return n}function p(n){var l=n.parentNode;l&&l.removeChild(n)}function y(l,u,t){var i,o,r,f={};for(r in u)"key"==r?i=u[r]:"ref"==r?o=u[r]:f[r]=u[r];if(arguments.length>2&&(f.children=arguments.length>3?n.call(arguments,2):t),"function"==typeof l&&null!=l.defaultProps)for(r in l.defaultProps)void 0===f[r]&&(f[r]=l.defaultProps[r]);return d(l,f,i,o,null)}function d(n,t,i,o,r){var f={type:n,props:t,key:i,ref:o,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,constructor:void 0,__v:null==r?++u:r,__i:-1,__u:0};return null==r&&null!=l.vnode&&l.vnode(f),f}function _(){return{current:null}}function g(n){return n.children}function b(n,l){this.props=n,this.context=l}function m(n,l){if(null==l)return n.__?m(n.__,n.__i+1):null;for(var u;lu&&i.sort(f));x.__r=0}function C(n,l,u,t,i,o,r,f,e,a,h){var v,p,y,d,_,g=t&&t.__k||s,b=l.length;for(u.__d=e,P(u,l,g),e=u.__d,v=0;v0?d(i.type,i.props,i.key,i.ref?i.ref:null,i.__v):i)?(i.__=n,i.__b=n.__b+1,f=H(i,u,r=t+a,s),i.__i=f,o=null,-1!==f&&(s--,(o=u[f])&&(o.__u|=131072)),null==o||null===o.__v?(-1==f&&a--,"function"!=typeof i.type&&(i.__u|=65536)):f!==r&&(f===r+1?a++:f>r?s>e-r?a+=f-r:a--:a=f(null!=e&&0==(131072&e.__u)?1:0))for(;r>=0||f=0){if((e=l[r])&&0==(131072&e.__u)&&i==e.key&&o===e.type)return r;r--}if(f2&&(e.children=arguments.length>3?n.call(arguments,2):t),d(l.type,e,i||l.key,o||l.ref,null)}function F(n,l){var u={__c:l="__cC"+e++,__:n,Consumer:function(n,l){return n.children(l)},Provider:function(n){var u,t;return this.getChildContext||(u=[],(t={})[l]=this,this.getChildContext=function(){return t},this.shouldComponentUpdate=function(n){this.props.value!==n.value&&u.some(function(n){n.__e=!0,w(n)})},this.sub=function(n){u.push(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){u.splice(u.indexOf(n),1),l&&l.call(n)}}),n.children}};return u.Provider.__=u.Consumer.contextType=u}n=s.slice,l={__e:function(n,l,u,t){for(var i,o,r;l=l.__;)if((i=l.__c)&&!i.__)try{if((o=i.constructor)&&null!=o.getDerivedStateFromError&&(i.setState(o.getDerivedStateFromError(n)),r=i.__d),null!=i.componentDidCatch&&(i.componentDidCatch(n,t||{}),r=i.__d),r)return i.__E=i}catch(l){n=l}throw n}},u=0,t=function(n){return null!=n&&null==n.constructor},b.prototype.setState=function(n,l){var u;u=null!=this.__s&&this.__s!==this.state?this.__s:this.__s=v({},this.state),"function"==typeof n&&(n=n(v({},u),this.props)),n&&v(u,n),null!=n&&this.__v&&(l&&this._sb.push(l),w(this))},b.prototype.forceUpdate=function(n){this.__v&&(this.__e=!0,n&&this.__h.push(n),w(this))},b.prototype.render=g,i=[],r="function"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,f=function(n,l){return n.__v.__b-l.__v.__b},x.__r=0,e=0;export{b as Component,g as Fragment,E as cloneElement,F as createContext,y as createElement,_ as createRef,y as h,B as hydrate,t as isValidElement,l as options,q as render,$ as toChildArray}; ++var n,l,u,t,i,o,r,f,e,c={},s=[],a=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,h=Array.isArray;function v(n,l){for(var u in l)Reflect.defineProperty(n,u,{value:l[u],writable:true,configurable:true,enumerable:true});return n}function p(n){var l=n.parentNode;l&&l.removeChild(n)}function y(l,u,t){var i,o,r,f={};for(r in u)"key"==r?i=u[r]:"ref"==r?o=u[r]:f[r]=u[r];if(arguments.length>2&&(f.children=arguments.length>3?n.call(arguments,2):t),"function"==typeof l&&null!=l.defaultProps)for(r in l.defaultProps)void 0===f[r]&&(f[r]=l.defaultProps[r]);return d(l,f,i,o,null)}function d(n,t,i,o,r){var f={type:n,props:t,key:i,ref:o,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,constructor:void 0,__v:null==r?++u:r,__i:-1,__u:0};return null==r&&null!=l.vnode&&l.vnode(f),f}function _(){return{current:null}}function g(n){return n.children}function b(n,l){this.props=n,this.context=l}function m(n,l){if(null==l)return n.__?m(n.__,n.__i+1):null;for(var u;lu&&i.sort(f));x.__r=0}function C(n,l,u,t,i,o,r,f,e,a,h){var v,p,y,d,_,g=t&&t.__k||s,b=l.length;for(u.__d=e,P(u,l,g),e=u.__d,v=0;v0?d(i.type,i.props,i.key,i.ref?i.ref:null,i.__v):i)?(i.__=n,i.__b=n.__b+1,f=H(i,u,r=t+a,s),i.__i=f,o=null,-1!==f&&(s--,(o=u[f])&&(o.__u|=131072)),null==o||null===o.__v?(-1==f&&a--,"function"!=typeof i.type&&(i.__u|=65536)):f!==r&&(f===r+1?a++:f>r?s>e-r?a+=f-r:a--:a=f(null!=e&&0==(131072&e.__u)?1:0))for(;r>=0||f=0){if((e=l[r])&&0==(131072&e.__u)&&i==e.key&&o===e.type)return r;r--}if(f2&&(e.children=arguments.length>3?n.call(arguments,2):t),d(l.type,e,i||l.key,o||l.ref,null)}function F(n,l){var u={__c:l="__cC"+e++,__:n,Consumer:function(n,l){return n.children(l)},Provider:function(n){var u,t;return this.getChildContext||(u=[],(t={})[l]=this,this.getChildContext=function(){return t},this.shouldComponentUpdate=function(n){this.props.value!==n.value&&u.some(function(n){n.__e=!0,w(n)})},this.sub=function(n){u.push(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){u.splice(u.indexOf(n),1),l&&l.call(n)}}),n.children}};return u.Provider.__=u.Consumer.contextType=u}n=s.slice,l={__e:function(n,l,u,t){for(var i,o,r;l=l.__;)if((i=l.__c)&&!i.__)try{if((o=i.constructor)&&null!=o.getDerivedStateFromError&&(i.setState(o.getDerivedStateFromError(n)),r=i.__d),null!=i.componentDidCatch&&(i.componentDidCatch(n,t||{}),r=i.__d),r)return i.__E=i}catch(l){n=l}throw n}},u=0,t=function(n){return null!=n&&null==n.constructor},b.prototype.setState=function(n,l){var u;u=null!=this.__s&&this.__s!==this.state?this.__s:this.__s=v({},this.state),"function"==typeof n&&(n=n(v({},u),this.props)),n&&v(u,n),null!=n&&this.__v&&(l&&this._sb.push(l),w(this))},b.prototype.forceUpdate=function(n){this.__v&&(this.__e=!0,n&&this.__h.push(n),w(this))},b.prototype.render=g,i=[],r="function"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,f=function(n,l){return n.__v.__b-l.__v.__b},x.__r=0,e=0;export{b as Component,g as Fragment,E as cloneElement,F as createContext,y as createElement,_ as createRef,y as h,B as hydrate,t as isValidElement,l as options,q as render,$ as toChildArray}; + //# sourceMappingURL=preact.module.js.map diff --git a/yarn.lock b/yarn.lock index 6cd64a3b5a..0aa28e5c28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10041,6 +10041,11 @@ posthtml@^0.15.1: posthtml-parser "^0.7.2" posthtml-render "^1.3.1" +preact@^10.19.2: + version "10.19.2" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.2.tgz#841797620dba649aaac1f8be42d37c3202dcea8b" + integrity sha512-UA9DX/OJwv6YwP9Vn7Ti/vF80XL+YA5H2l7BpCtUr3ya8LWHFzpiO5R+N7dN16ujpIxhekRFuOOF82bXX7K/lg== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"