From 65a5edf26b4bf85fa54f5a44eab09f22db789692 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sun, 12 Nov 2023 11:35:36 -0700 Subject: [PATCH 01/27] allow to customize the push-server for testing --- src/App.vue | 58 +++++++++++++++++++++++++++------ src/constants/app.ts | 13 ++++++-- src/db/index.ts | 3 ++ src/db/tables/settings.ts | 2 +- src/views/AccountViewView.vue | 60 ++++++++++++++++++++++++++++++----- 5 files changed, 115 insertions(+), 21 deletions(-) diff --git a/src/App.vue b/src/App.vue index b732aee..188f689 100644 --- a/src/App.vue +++ b/src/App.vue @@ -263,19 +263,59 @@ import { Vue, Component } from "vue-facing-decorator"; import axios from "axios"; +import { AppString } from "@/constants/app"; +import { db } from "@/db/index"; +import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; + +interface Notification { + group: string; + type: string; + title: string; + text: string; +} + @Component export default class App extends Vue { + $notify!: (notification: Notification, timeout?: number) => void; + b64 = ""; - mounted() { - axios - .get("https://timesafari-pwa.anomalistlabs.com/web-push/vapid") - .then((response) => { - this.b64 = response.data.vapidKey; - console.log(this.b64); - }) - .catch((error) => { - console.error("API error", error); + + async mounted() { + try { + await db.open(); + const settings = await db.settings.get(MASTER_SETTINGS_KEY); + let pushUrl: string = AppString.DEFAULT_PUSH_SERVER; + if (settings?.pushServer) { + pushUrl = settings.pushServer; + } + + await axios.get(pushUrl + "/web-push/vapid").then((response) => { + this.b64 = response.data?.vapidKey; + console.log("Got vapid key:", this.b64); }); + if (!this.b64) { + this.$notify( + { + group: "alert", + type: "danger", + title: "Error Setting Notifications", + text: "Could not set notifications.", + }, + -1, + ); + } + } catch (error) { + console.error("Got an error initializing notifications:", error); + this.$notify( + { + group: "alert", + type: "danger", + title: "Error Setting Notifications", + text: "Got an error setting notifications.", + }, + -1, + ); + } } private askPermission(): Promise { diff --git a/src/constants/app.ts b/src/constants/app.ts index 270277d..313e472 100644 --- a/src/constants/app.ts +++ b/src/constants/app.ts @@ -4,20 +4,27 @@ * See also ../libs/veramo/setup.ts */ export enum AppString { - APP_NAME = "TimeSafari", + APP_NAME = "Time Safari", PROD_ENDORSER_API_SERVER = "https://api.endorser.ch", TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch", LOCAL_ENDORSER_API_SERVER = "http://localhost:3000", DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER, + + PROD_PUSH_SERVER = "https://push.endorser.ch", + TEST_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com", + LOCAL_PUSH_SERVER = "http://localhost:3001", + + DEFAULT_PUSH_SERVER = TEST_PUSH_SERVER, } /** - * See notiwind package + * The possible values for "group" and "type" are in App.vue. + * From the notiwind package */ export interface NotificationIface { - group: string; + group: string; // "alert" | "modal" type: string; // "toast" | "info" | "success" | "warning" | "danger" title: string; text: string; diff --git a/src/db/index.ts b/src/db/index.ts index 37d6cf5..ea8556c 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -45,5 +45,8 @@ db.on("populate", () => { db.settings.add({ id: MASTER_SETTINGS_KEY, apiServer: AppString.DEFAULT_ENDORSER_API_SERVER, + + // remember that things you add from now on aren't automatically in the DB for old users + pushServer: AppString.DEFAULT_PUSH_SERVER, }); }); diff --git a/src/db/tables/settings.ts b/src/db/tables/settings.ts index 41196de..20c378e 100644 --- a/src/db/tables/settings.ts +++ b/src/db/tables/settings.ts @@ -20,9 +20,9 @@ export type Settings = { lastViewedClaimId?: string; // Last viewed claim ID lastNotifiedClaimId?: string; // Last notified claim ID isRegistered?: boolean; + pushServer?: string; // Push server URL // Array of named search boxes defined by bounding boxes - searchBoxes?: Array<{ name: string; bbox: BoundingBox; diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index f222fc3..9bd7c73 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -324,19 +324,55 @@ + + +
+

+ Notification Push Server +

+ + + + + @@ -346,7 +382,7 @@ From ee6a344dafe873d03efe7814566546e1d32f3c9d Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sun, 12 Nov 2023 19:03:39 -0700 Subject: [PATCH 02/27] doc: add a guess for the states of the notifications --- web-push.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web-push.md b/web-push.md index 1869d95..2ca35cd 100644 --- a/web-push.md +++ b/web-push.md @@ -390,3 +390,13 @@ While notifications are turned on, the user can tap on the App Notifications tog - "Turn off Notifications" to fully turn them off (which means the user will need to go through the dialogs agains to turn them back on). - "Leave it On" to make no changes and dismiss the dialog. + +# NOTIFICATION STATES + +* Unpermissioned. Push server cannot send notifications to the user because it does not have permission. + This may be the same as when the user gave permission in the past but has since revoked it at the OS or browser + level, outside the app. (User can change to Permissioned when the user gives permission.) +* Permissioned. (User can change to Unpermissioned via the OS or browser settings.) + * Active. (User can change to Muted when the user mutes notifications.) + * Muted. (User can change to Active when the user toggles it.) + (Turning mute off automatically after some amount of time is not planned in version 1.) From 3bf8fd0c220200a829ce3b5020bece31d4eb6a45 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sat, 2 Dec 2023 15:28:32 -0700 Subject: [PATCH 03/27] rename "push" to "webPush" for future-proofing --- src/App.vue | 4 ++-- src/db/index.ts | 2 +- src/db/tables/settings.ts | 2 +- src/views/AccountViewView.vue | 22 +++++++++++----------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/App.vue b/src/App.vue index 188f689..2486884 100644 --- a/src/App.vue +++ b/src/App.vue @@ -285,8 +285,8 @@ export default class App extends Vue { await db.open(); const settings = await db.settings.get(MASTER_SETTINGS_KEY); let pushUrl: string = AppString.DEFAULT_PUSH_SERVER; - if (settings?.pushServer) { - pushUrl = settings.pushServer; + if (settings?.webPushServer) { + pushUrl = settings.webPushServer; } await axios.get(pushUrl + "/web-push/vapid").then((response) => { diff --git a/src/db/index.ts b/src/db/index.ts index ea8556c..16c99d8 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -47,6 +47,6 @@ db.on("populate", () => { apiServer: AppString.DEFAULT_ENDORSER_API_SERVER, // remember that things you add from now on aren't automatically in the DB for old users - pushServer: AppString.DEFAULT_PUSH_SERVER, + webPushServer: AppString.DEFAULT_PUSH_SERVER, }); }); diff --git a/src/db/tables/settings.ts b/src/db/tables/settings.ts index 20c378e..0b7960c 100644 --- a/src/db/tables/settings.ts +++ b/src/db/tables/settings.ts @@ -20,7 +20,7 @@ export type Settings = { lastViewedClaimId?: string; // Last viewed claim ID lastNotifiedClaimId?: string; // Last notified claim ID isRegistered?: boolean; - pushServer?: string; // Push server URL + webPushServer?: string; // Web Push server URL // Array of named search boxes defined by bounding boxes searchBoxes?: Array<{ diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index 9bd7c73..9e29080 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -349,10 +349,10 @@ @@ -427,8 +427,8 @@ export default class AccountViewView extends Vue { numAccounts = 0; publicHex = ""; publicBase64 = ""; - pushServer = ""; - pushServerInput = ""; + webPushServer = ""; + webPushServerInput = ""; limits: RateLimits | null = null; limitsMessage = ""; loadingLimits = true; // might as well now that we do it on mount, to avoid flashing the registration message @@ -553,8 +553,8 @@ export default class AccountViewView extends Vue { (settings?.firstName || "") + (settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3 this.isRegistered = !!settings?.isRegistered; - this.pushServer = (settings?.pushServer as string) || ""; - this.pushServerInput = (settings?.pushServer as string) || ""; + this.webPushServer = (settings?.webPushServer as string) || ""; + this.webPushServerInput = (settings?.webPushServer as string) || ""; this.showContactGives = !!settings?.showContactGivesInline; } @@ -886,9 +886,9 @@ export default class AccountViewView extends Vue { async onClickSavePushServer() { await db.open(); db.settings.update(MASTER_SETTINGS_KEY, { - pushServer: this.pushServerInput, + webPushServer: this.webPushServerInput, }); - this.pushServer = this.pushServerInput; + this.webPushServer = this.webPushServerInput; } } From 681d949098386e3a1a462411f1ebd0b65f326f25 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sat, 2 Dec 2023 15:35:44 -0700 Subject: [PATCH 04/27] update web push servers to the domains we're using --- src/constants/app.ts | 8 ++++---- src/views/AccountViewView.vue | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/constants/app.ts b/src/constants/app.ts index 313e472..f4be364 100644 --- a/src/constants/app.ts +++ b/src/constants/app.ts @@ -12,11 +12,11 @@ export enum AppString { DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER, - PROD_PUSH_SERVER = "https://push.endorser.ch", - TEST_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com", - LOCAL_PUSH_SERVER = "http://localhost:3001", + PROD_PUSH_SERVER = "https://timesafari.app", + TEST1_PUSH_SERVER = "https://test.timesafari.app", + TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com", - DEFAULT_PUSH_SERVER = TEST_PUSH_SERVER, + DEFAULT_PUSH_SERVER = TEST1_PUSH_SERVER, } /** diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index 9e29080..f0ee90a 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -323,19 +323,19 @@
From 4041a7d08e9ff240c928a05e22b66d8e7f7159fb Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sat, 2 Dec 2023 23:15:50 -0700 Subject: [PATCH 05/27] more commentary, including for blank values for the user --- CHANGELOG.md | 4 +++- README.md | 7 ++++--- src/views/AccountViewView.vue | 5 +++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d2b26..581683a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Web push notifications -## [0.1.3] - 2023.11 +## [0.1.3] - 2023.11.08 - 910f57ec7d2e50803ae3d04f4b927e0f5219fbde ### Added - Contact name editing ### Changed diff --git a/README.md b/README.md index 76c09f3..d3e6241 100644 --- a/README.md +++ b/README.md @@ -90,9 +90,10 @@ See [Configuration Reference](https://cli.vuejs.org/config/). ### Clear data & restart -Clear cache for localhost, then go to http://localhost:8080/start -(because it'll generate a new one automatically if you start on the `/account` page). - +* Clear cache for localhost, then go to http://localhost:8080/start + (because it'll generate a new one automatically if you start on the `/account` page). +* Unregister service worker (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers` or `about:debugging`). +* Clear notifications (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search). ## Other diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index f0ee90a..08083fb 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -377,6 +377,11 @@ Use Test 2 + + When that setting is blank, this app will use the default web push + server URL: + {{ AppConstants.DEFAULT_PUSH_SERVER }} + From 73354121453ad3d78f75817be419267324b7c6bf Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Mon, 4 Dec 2023 09:31:24 -0700 Subject: [PATCH 06/27] revert type complaint, which is opposite from previous suggestion, which 8-S --- src/views/ProjectViewView.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index cb6cf97..0bd1471 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -625,7 +625,7 @@ export default class ProjectViewView extends Vue { } openOfferDialog() { - (this.$refs.customOfferDialog as typeof OfferDialog).open(); + (this.$refs.customOfferDialog as OfferDialog).open(); } } From 5cbf917ada5d85becf14e176982c1e60d8779657 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Mon, 4 Dec 2023 09:34:27 -0700 Subject: [PATCH 07/27] don't show non-message to user; fix API server setting; misc doc & task stuff --- project.task.yaml | 7 ++----- src/views/HomeView.vue | 2 +- sw_scripts/additional-scripts.js | 26 +++++++++++++++----------- sw_scripts/safari-notifications.js | 11 +++++------ 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/project.task.yaml b/project.task.yaml index 9b56584..aff2cca 100644 --- a/project.task.yaml +++ b/project.task.yaml @@ -1,11 +1,6 @@ tasks: -- remove hard-coded anomalistlabs.com - -- don't show "Give" & "Offer" on project screen if they don't have an identifier -- allow some gives even if they aren't registered - - in endorser-push-server - mount folder for persistent sqlite DB outside of container - extract private_key_hex in webpush.py - 40 notifications : @@ -40,6 +35,7 @@ tasks: - bug (that is hard to reproduce) - on the second 'give' recorded on prod it showed me as the agent - make identicons for contacts into more-memorable faces (and maybe change project identicons, too) - allow download of each VC (to show that they can actually own their data) +- allow some gives even if they aren't registered - contacts v+ : - 01 Import all the non-sensitive data (ie. contacts & settings). @@ -69,6 +65,7 @@ tasks: - .5 show seed phrase in a QR code for transfer to another device - .5 on DiscoverView, switch to a filter UI (eg. just from friend +- .5 don't show "Offer" on project screen if they aren't registered - 24 Move to Vite - 32 accept images for projects diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index af00edc..21b6ac9 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -104,7 +104,7 @@ class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm" v-if="record.jwtId == feedLastViewedId" > - You've seen all the following before + You've seen all the following
diff --git a/sw_scripts/additional-scripts.js b/sw_scripts/additional-scripts.js index e39c527..76aad46 100644 --- a/sw_scripts/additional-scripts.js +++ b/sw_scripts/additional-scripts.js @@ -5,7 +5,7 @@ importScripts( ); self.addEventListener("install", (event) => { - console.error(event); + console.error("Adding event listener for:", event); importScripts( "safari-notifications.js", "nacl.js", @@ -23,16 +23,20 @@ self.addEventListener("push", function (event) { payload = JSON.parse(event.data.text()); } const message = await self.getNotificationCount(); - console.error(message); - const title = payload ? payload.title : "Custom Title"; - const options = { - body: message, - icon: payload ? payload.icon : "icon.png", - badge: payload ? payload.badge : "badge.png", - }; - await self.registration.showNotification(title, options); + if (message) { + console.log("Will notify user:", message); + const title = payload ? payload.title : "Custom Title"; + const options = { + body: message, + icon: payload ? payload.icon : "icon.png", + badge: payload ? payload.badge : "badge.png", + }; + await self.registration.showNotification(title, options); + } else { + console.log("No notification message, so will not tell the user."); + } } catch (error) { - console.error("Error in processing the push event:", error); + console.error("Error processing the push event:", error); } })(), ); @@ -51,7 +55,7 @@ self.addEventListener("activate", (event) => { }); self.addEventListener("fetch", (event) => { - console.log(event.request); + console.log("Got fetch event", event.request); }); self.addEventListener("error", (event) => { diff --git a/sw_scripts/safari-notifications.js b/sw_scripts/safari-notifications.js index f0baf83..fb31f18 100644 --- a/sw_scripts/safari-notifications.js +++ b/sw_scripts/safari-notifications.js @@ -466,6 +466,7 @@ async function getNotificationCount() { if ("secret" in self) { secret = self.secret; const secretUint8Array = self.decodeBase64(secret); + // 1 is our master settings ID; see MASTER_SETTINGS_KEY const settings = await getSettingById(1); let lastNotifiedClaimId = null; if ("lastNotifiedClaimId" in settings) { @@ -496,7 +497,7 @@ async function getNotificationCount() { headers["Authorization"] = "Bearer " + (await accessToken(identifier)); let response = await fetch( - "https://test-api.endorser.ch/api/v2/report/claims", + settings["apiServer"] + "/api/v2/report/claims", { method: "GET", headers: headers, @@ -513,15 +514,13 @@ async function getNotificationCount() { } newClaims++; } - if (newClaims === 0) { - result = "You have no new claims today."; - } else { - result = `${newClaims} have been shared with you`; + if (newClaims > 0) { + result = `There are ${newClaims} new activities on TimeSafari`; } const most_recent_notified = claims[0]["id"]; await setMostRecentNotified(most_recent_notified); } else { - console.error(response.status); + console.error("The service worker got a bad response status when fetching claims:", response.status, response); } break; } From ce5933f6455508dd996cab5f3b36637946baef2c Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Mon, 4 Dec 2023 13:29:16 -0700 Subject: [PATCH 08/27] remove visibility to contact operations where there is no activeDid --- project.task.yaml | 3 -- src/views/ContactsView.vue | 100 ++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/project.task.yaml b/project.task.yaml index aff2cca..f22b95d 100644 --- a/project.task.yaml +++ b/project.task.yaml @@ -1,13 +1,10 @@ tasks: -- in endorser-push-server - mount folder for persistent sqlite DB outside of container - extract private_key_hex in webpush.py - 40 notifications : - push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew -- .2 change the "claims" verbiage in feeds (eg. safari-notifications.js) -- .5 allow to manage their notifications even without an identity - 01 Ensure each action sent to the server has a confirmation - eg registration (ie a toast something that dismisses after 5-10s) - .3 fix the Project-location-selection map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index c7ca1df..0e75ad0 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -110,48 +110,50 @@
- - - - - - + + class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md" + @click="setVisibility(contact, true, true)" + title="They cannot see you" + > + + + + +
-
- -
-

Claim Server

+

+ How do I access even more functionality? +

+

+ There is an "Advanced" section at the bottom of the Account + page. +

+

+ There is a even more functionality in a mobile app (and more + documentation) at + + EndorserSearch.com + +

+

What is your privacy policy?

See diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index b58f754..6232592 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -56,8 +56,11 @@

{{ truncatedDesc }} - Read More... Read More
@@ -65,7 +68,7 @@ Read Less- Read Less
@@ -167,8 +170,10 @@ {{ didInfo(offer.agentDid, activeDid, allMyDids, allContacts) }} - - {{ offer.amount }} + {{ offer.amount }}
@@ -195,9 +200,11 @@ > {{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }} - - {{ give.amount }} + + {{ give.amount }}
@@ -625,5 +632,24 @@ export default class ProjectViewView extends Vue { openOfferDialog() { (this.$refs.customOfferDialog as OfferDialog).open(); } + + UNIT_CODES: Record> = { + BTC: { + name: "Bitcoin", + faIcon: "bitcoin-sign", + }, + HUR: { + name: "hours", + faIcon: "clock", + }, + USD: { + name: "US Dollars", + faIcon: "dollar", + }, + }; + + iconForUnitCode(unitCode: string) { + return this.UNIT_CODES[unitCode]?.faIcon || "question"; + } } From 3f436476a2183400af2d1868941be9b1a0c46627 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Tue, 5 Dec 2023 18:47:56 -0700 Subject: [PATCH 18/27] fix project loading & saving to include all the claim data --- project.task.yaml | 1 + src/views/NewEditProjectView.vue | 35 ++++++++++++++++---------------- src/views/ProjectViewView.vue | 2 +- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/project.task.yaml b/project.task.yaml index c0bc9f8..828b340 100644 --- a/project.task.yaml +++ b/project.task.yaml @@ -17,6 +17,7 @@ tasks: - .5 make a VC details page, or link to endorser.ch (including confirmations) - .3 Add URL for project +- .5 Add start date to project - 01 allow download of each VC (& confirmations, to show that they actually own their data) - .3 check that Android shows "back" buttons on screens without bottom tray - .1 Make give description text box into something that expands as they type? diff --git a/src/views/NewEditProjectView.vue b/src/views/NewEditProjectView.vue index 62d5c71..ec8f56e 100644 --- a/src/views/NewEditProjectView.vue +++ b/src/views/NewEditProjectView.vue @@ -26,18 +26,18 @@ type="text" placeholder="Idea Name" class="block w-full rounded border border-slate-400 mb-4 px-3 py-2" - v-model="projectName" + v-model="fullClaim.name" />
- {{ description.length }}/5000 max. characters + {{ fullClaim.description.length }}/5000 max. characters
@@ -72,7 +72,7 @@ name="OpenStreetMap" /> @@ -136,13 +136,17 @@ export default class NewEditProjectView extends Vue { activeDid = ""; apiServer = ""; - description = ""; errorMessage = ""; + fullClaim: PlanVerifiableCredential = { + "@context": "https://schema.org", + "@type": "PlanAction", + name: "", + description: "", + }; includeLocation = false; latitude = 0; longitude = 0; numAccounts = 0; - projectName = ""; zoom = 2; async beforeCreate() { @@ -214,9 +218,12 @@ export default class NewEditProjectView extends Vue { try { const resp = await this.axios.get(url, { headers }); if (resp.status === 200) { - const claim = resp.data.claim; - this.projectName = claim.name; - this.description = claim.description; + this.fullClaim = resp.data.claim; + if (this.fullClaim?.location) { + this.includeLocation = true; + this.latitude = this.fullClaim.location.geo.latitude; + this.longitude = this.fullClaim.location.geo.longitude; + } } } catch (error) { console.error("Got error retrieving that project", error); @@ -225,13 +232,7 @@ export default class NewEditProjectView extends Vue { private async SaveProject(identity: IIdentifier) { // Make a claim - const vcClaim: PlanVerifiableCredential = { - "@context": "https://schema.org", - "@type": "PlanAction", - name: this.projectName, - description: this.description, - identifier: this.projectId || undefined, - }; + const vcClaim: PlanVerifiableCredential = this.fullClaim; if (this.projectId) { vcClaim.identifier = this.projectId; } @@ -314,8 +315,8 @@ export default class NewEditProjectView extends Vue { error?: { message?: string }; }>; if (serverError) { + console.log("Got error from server", serverError); if (Object.prototype.hasOwnProperty.call(serverError, "message")) { - console.log(serverError); userMessage = serverError.response?.data?.error?.message || ""; // This is info for the user. this.$notify( { diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index 6232592..8226f24 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -35,7 +35,7 @@ {{ issuer }}
-
+
{{ timeSince }}
From bb122be3194d3d77221d5aa3a29c88261fa19c9a Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Tue, 5 Dec 2023 19:55:44 -0700 Subject: [PATCH 19/27] add URL for plans --- project.task.yaml | 4 ++-- src/libs/endorserServer.ts | 6 +++++ src/libs/util.ts | 3 +++ src/main.ts | 2 ++ src/views/NewEditProjectView.vue | 8 ++++++- src/views/ProjectViewView.vue | 41 ++++++++++++++++++++++++++++++-- 6 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 src/libs/util.ts diff --git a/project.task.yaml b/project.task.yaml index 828b340..5e89218 100644 --- a/project.task.yaml +++ b/project.task.yaml @@ -11,10 +11,10 @@ tasks: - .5 Add infinite scroll to gifts on the home page -- Discuss whether the remaining tasks are worthwhile before MVP release. - - .5 If notifications are not enabled, add message to front page with link/button to enable +- Discuss whether the remaining tasks are worthwhile before MVP release. + - .5 make a VC details page, or link to endorser.ch (including confirmations) - .3 Add URL for project - .5 Add start date to project diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index 8db51eb..792eab2 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -69,6 +69,8 @@ export interface OfferServerRecord { validThrough: string; } +// Note that previous VCs may have additional fields. +// https://endorser.ch/doc/html/transactions.html#id4 export interface GiveVerifiableCredential { "@context"?: string; // optional when embedded, eg. in an Agree "@type": "GiveAction"; @@ -80,6 +82,8 @@ export interface GiveVerifiableCredential { recipient?: { identifier: string }; } +// Note that previous VCs may have additional fields. +// https://endorser.ch/doc/html/transactions.html#id8 export interface OfferVerifiableCredential { "@context"?: string; // optional when embedded, eg. in an Agree "@type": "Offer"; @@ -93,6 +97,8 @@ export interface OfferVerifiableCredential { validThrough?: string; } +// Note that previous VCs may have additional fields. +// https://endorser.ch/doc/html/transactions.html#id7 export interface PlanVerifiableCredential { "@context": "https://schema.org"; "@type": "PlanAction"; diff --git a/src/libs/util.ts b/src/libs/util.ts new file mode 100644 index 0000000..693a331 --- /dev/null +++ b/src/libs/util.ts @@ -0,0 +1,3 @@ +export const isGlobalUri = (uri: string) => { + return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/)); +}; diff --git a/src/main.ts b/src/main.ts index de867e1..997d572 100644 --- a/src/main.ts +++ b/src/main.ts @@ -36,6 +36,7 @@ import { faFloppyDisk, faFolderOpen, faGift, + faGlobe, faHand, faHouseChimney, faLocationDot, @@ -86,6 +87,7 @@ library.add( faFloppyDisk, faFolderOpen, faGift, + faGlobe, faHand, faHouseChimney, faLocationDot, diff --git a/src/views/NewEditProjectView.vue b/src/views/NewEditProjectView.vue index ec8f56e..eaff9b0 100644 --- a/src/views/NewEditProjectView.vue +++ b/src/views/NewEditProjectView.vue @@ -40,6 +40,12 @@ {{ fullClaim.description.length }}/5000 max. characters
+ +
- Map View + >Map View + +
+
@@ -272,6 +277,7 @@ import { accountsDB, db } from "@/db/index"; import { Contact } from "@/db/tables/contacts"; import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { accessToken } from "@/libs/crypto"; +import { isGlobalUri } from "@/libs/util"; import { didInfo, GiverInputInfo, @@ -314,6 +320,7 @@ export default class ProjectViewView extends Vue { timeSince = ""; truncatedDesc = ""; truncateLength = 40; + url = ""; async created() { await db.open(); @@ -415,6 +422,7 @@ export default class ProjectViewView extends Vue { this.truncatedDesc = this.description.slice(0, this.truncateLength); this.latitude = resp.data.claim?.location?.geo?.latitude || 0; this.longitude = resp.data.claim?.location?.geo?.longitude || 0; + this.url = resp.data.claim?.url || ""; } else if (resp.status === 404) { // actually, axios throws an error so we never get here this.$notify( @@ -651,5 +659,34 @@ export default class ProjectViewView extends Vue { iconForUnitCode(unitCode: string) { return this.UNIT_CODES[unitCode]?.faIcon || "question"; } + + // return an HTTPS URL if it's not a global URL + addScheme(url: string) { + if (!isGlobalUri(url)) { + return "https://" + url; + } + return url; + } + + // return just the domain for display, if possible + domainForWebsite(url: string) { + try { + const hostname = new URL(url).hostname; + console.log("hostname", hostname); + if (!hostname) { + // happens for non-http URLs + return url; + } else if (url.endsWith(hostname)) { + // it's just the domain + return hostname; + } else { + // there's more, but don't bother displaying the whole thing + return hostname + "..."; + } + } catch (error: unknown) { + // must not be a valid URL + return url; + } + } } From c6e10bfdad80d5e08d3c21897ca6413ed7a80220 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Tue, 5 Dec 2023 20:03:19 -0700 Subject: [PATCH 20/27] update tasks --- project.task.yaml | 40 +++++++++++++++++------------------ src/views/ProjectViewView.vue | 1 - 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/project.task.yaml b/project.task.yaml index 5e89218..3080bad 100644 --- a/project.task.yaml +++ b/project.task.yaml @@ -13,15 +13,30 @@ tasks: - .5 If notifications are not enabled, add message to front page with link/button to enable -- Discuss whether the remaining tasks are worthwhile before MVP release. +- show VC details... somehow: + - .5 make a VC details page, or link to endorser.ch (including confirmations) + - 01 allow download of each VC (& confirmations, to show that they actually own their data) + - 04 allow user to download VCs, mine + ones I can see about me from others + - add VC confirmation? -- .5 make a VC details page, or link to endorser.ch (including confirmations) -- .3 Add URL for project +- Release Minimum Viable Product : + - generate new webpush.db entries, data/webpush.db private_key_hex & subscription_info & vapid_claims email + - .5 deploy endorser.ch server above Dec 1 (to get plan searches by names as well as descriptions) + - 08 thorough testing for errors & edge cases + - 01 ensure ability to recover server remotely, and add redundant access + - Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot). + - Add disclaimers. + - Switch default server to the public server. + - Deploy to a server. + - Ensure public server has limits that work for group adoption. + - Test PWA features on Android and iOS. + blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time + +- make identicons for contacts into more-memorable faces (and maybe change project identicons, too) +- allow some gives even if they aren't registered - .5 Add start date to project -- 01 allow download of each VC (& confirmations, to show that they actually own their data) - .3 check that Android shows "back" buttons on screens without bottom tray - .1 Make give description text box into something that expands as they type? -- 04 allow user to download claims, mine + ones I can see about me from others - .5 customize favicon assignee-group:ui - .2 Show a warning if both giver and recipient are the same (but still allow?) - 01 Would it look better to shrink the buttons on many pages so they don't expand to the width of the screen? assignee-group:ui @@ -29,8 +44,6 @@ tasks: - .5 include the hash of the latest commit on help page next to version (maybe Trent's git-hash branch) - .5 remove references to localStorage for projectId (now that it's pulling from the path) - bug (that is hard to reproduce) - on the second 'give' recorded on prod it showed me as the agent -- make identicons for contacts into more-memorable faces (and maybe change project identicons, too) -- allow some gives even if they aren't registered - switch some checks for activeDid to check for isRegistered - .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show" - .5 fix cert generation on server (since it didn't happen automatically for Nov 30) @@ -46,19 +59,6 @@ tasks: - maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version") - 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie) -- Release Minimum Viable Product : - - generate new webpush.db entries, data/webpush.db private_key_hex & subscription_info & vapid_claims email - - .5 deploy endorser.ch server above Dec 1 (to get plan searches by names as well as descriptions) - - 08 thorough testing for errors & edge cases - - 01 ensure ability to recover server remotely, and add redundant access - - Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot). - - Add disclaimers. - - Switch default server to the public server. - - Deploy to a server. - - Ensure public server has limits that work for group adoption. - - Test PWA features on Android and iOS. - blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time - - .5 show seed phrase in a QR code for transfer to another device - .5 on DiscoverView, switch to a filter UI (eg. just from friend - .5 don't show "Offer" on project screen if they aren't registered diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index 1087fa7..a72ff9d 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -672,7 +672,6 @@ export default class ProjectViewView extends Vue { domainForWebsite(url: string) { try { const hostname = new URL(url).hostname; - console.log("hostname", hostname); if (!hostname) { // happens for non-http URLs return url; From 4daffe8f404aa8d5dbc3cabd3220be6d4775ceb5 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Wed, 6 Dec 2023 14:35:57 -0700 Subject: [PATCH 21/27] doc: fix note about remaining py_push_server work --- project.task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.task.yaml b/project.task.yaml index 3080bad..67c48e5 100644 --- a/project.task.yaml +++ b/project.task.yaml @@ -20,7 +20,7 @@ tasks: - add VC confirmation? - Release Minimum Viable Product : - - generate new webpush.db entries, data/webpush.db private_key_hex & subscription_info & vapid_claims email + - generate new webpush.db entry, webpush.py private_key_hex & subscription_info & vapid_claims email - .5 deploy endorser.ch server above Dec 1 (to get plan searches by names as well as descriptions) - 08 thorough testing for errors & edge cases - 01 ensure ability to recover server remotely, and add redundant access From 1e2ad8554703e06c5ecd0edcc1d9c4ff23a2377b Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Thu, 7 Dec 2023 20:33:17 -0700 Subject: [PATCH 22/27] add dedicated help page for looking into notifications --- src/router/index.ts | 8 ++++ src/views/HelpNotificationsView.vue | 65 +++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/views/HelpNotificationsView.vue diff --git a/src/router/index.ts b/src/router/index.ts index 49b3376..af3e32c 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -91,6 +91,14 @@ const routes: Array = [ component: () => import(/* webpackChunkName: "help" */ "../views/HelpView.vue"), }, + { + path: "/help-notifications", + name: "help-notifications", + component: () => + import( + /* webpackChunkName: "help-notifications" */ "../views/HelpNotificationsView.vue" + ), + }, { path: "/identity-switcher", name: "identity-switcher", diff --git a/src/views/HelpNotificationsView.vue b/src/views/HelpNotificationsView.vue new file mode 100644 index 0000000..ce9c0a1 --- /dev/null +++ b/src/views/HelpNotificationsView.vue @@ -0,0 +1,65 @@ + + From a6bb036cebe01794d55ce5b1f0a5164fbd26040a Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Thu, 7 Dec 2023 20:35:29 -0700 Subject: [PATCH 23/27] fix name of new HelpNotificationsView class --- src/views/HelpNotificationsView.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/HelpNotificationsView.vue b/src/views/HelpNotificationsView.vue index ce9c0a1..ed4458a 100644 --- a/src/views/HelpNotificationsView.vue +++ b/src/views/HelpNotificationsView.vue @@ -59,7 +59,7 @@ interface Notification { } @Component({ components: { QuickNav } }) -export default class Help extends Vue { +export default class HelpNotificationsView extends Vue { $notify!: (notification: Notification, timeout?: number) => void; } From 6045975b79d310bc1f0a2afd504b6ed228a2e3f6 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Thu, 7 Dec 2023 20:44:48 -0700 Subject: [PATCH 24/27] add more tasks for notifications work --- project.task.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/project.task.yaml b/project.task.yaml index 67c48e5..40ff1fb 100644 --- a/project.task.yaml +++ b/project.task.yaml @@ -6,6 +6,9 @@ tasks: - extract private_key_hex in py-push-server webpush.py - lock down regenerate_vapid endpoint (so only we admins can do it on demand) - remove sleep in py-push-server app.py + - revisit "maybe" and "never" buttons on accont screen + - see if we can detect OS-level notifications if turned off + - write troubleshooting docs for notifications - .3 fix the Project-location-selection map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui From 098ef3c6447ef5a70145b9c3af62aa0542b6f489 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Fri, 8 Dec 2023 11:40:09 -0700 Subject: [PATCH 25/27] add Claim view for details about a specific server record --- README.md | 5 +- package-lock.json | 124 +- package.json | 1 + src/libs/endorserServer.ts | 22 +- src/libs/util.ts | 2 + src/main.ts | 2 + src/router/index.ts | 6 + src/util.d.ts | 2184 +++++++++++++++++++++++++++++++++ src/views/ClaimView.vue | 411 +++++++ src/views/ContactsView.vue | 4 +- src/views/HomeView.vue | 11 +- src/views/ProjectViewView.vue | 11 +- 12 files changed, 2727 insertions(+), 56 deletions(-) create mode 100644 src/util.d.ts create mode 100644 src/views/ClaimView.vue diff --git a/README.md b/README.md index f23c321..2c8f010 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Under the "Your Identity" screen, click "Advanced", click "Switch Identity / No ### Web-push -For your own web-push tests, change the 'vapid' URL in App.vue, and install apps on the same domain. +For your own web-push tests, change the push server URL in Advanced settings on the account page, and install Time Safari & push server on the same domain. ### Icons @@ -98,9 +98,10 @@ To add an icon, add to main.ts and reference with `fa` element and `icon` attrib ### Clear/Reset data & restart -* Clear cache for localhost. +* Clear cache for site. (In Chrome, go to `chrome://settings/cookies` and "all site data and permissions"; in Firefox, go to `about:preferences` and search for cache.) * Unregister service worker (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers` or `about:debugging`). * Clear notification permission (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search). +* Clear Cache Storage (in Chrome, in dev tools under Application; in Firefox, in dev tools under Storage). diff --git a/package-lock.json b/package-lock.json index 75f460e..7a97890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "reflect-metadata": "^0.1.13", "register-service-worker": "^1.7.2", "three": "^0.156.1", + "util": "^0.12.5", "vue": "^3.3.4", "vue-axios": "^3.5.2", "vue-facing-decorator": "^3.0.2", @@ -72,13 +73,13 @@ "@vue/cli-service": "~5.0.8", "@vue/eslint-config-typescript": "^11.0.3", "autoprefixer": "^10.4.15", - "eslint": "^8.48.0", + "eslint": "^8.53.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-vue": "^9.17.0", "leaflet": "^1.9.4", "postcss": "^8.4.29", - "prettier": "^3.0.3", + "prettier": "^3.1.0", "tailwindcss": "^3.3.3", "typescript": "~5.2.2" } @@ -2821,9 +2822,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -2871,9 +2872,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", + "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -5498,12 +5499,12 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -5525,9 +5526,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@jest/create-cache-key-function": { @@ -8819,11 +8820,11 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz", - "integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==", + "version": "20.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", + "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", "dependencies": { - "undici-types": "~5.25.1" + "undici-types": "~5.26.4" } }, "node_modules/@types/normalize-package-data": { @@ -9206,6 +9207,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@unimodules/core": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@unimodules/core/-/core-7.1.2.tgz", @@ -11245,7 +11252,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12063,7 +12069,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "devOptional": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -14220,18 +14225,19 @@ } }, "node_modules/eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", + "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.55.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -15891,7 +15897,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -16151,7 +16156,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "devOptional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -16204,7 +16208,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "devOptional": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -16355,7 +16358,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -16426,7 +16428,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "devOptional": true, "engines": { "node": ">= 0.4.0" } @@ -16465,7 +16466,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "devOptional": true, "engines": { "node": ">= 0.4" }, @@ -16477,7 +16477,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "devOptional": true, "engines": { "node": ">= 0.4" }, @@ -16489,7 +16488,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -17081,6 +17079,21 @@ "node": ">= 0.10" } }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -17152,7 +17165,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -17257,6 +17269,20 @@ "node": ">=4" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -17530,7 +17556,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, "dependencies": { "which-typed-array": "^1.1.11" }, @@ -23137,9 +23162,9 @@ } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", + "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -26870,9 +26895,9 @@ } }, "node_modules/undici-types": { - "version": "5.25.3", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -27055,6 +27080,18 @@ "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -27966,7 +28003,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", diff --git a/package.json b/package.json index 0abdcc4..fa0bcb5 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "reflect-metadata": "^0.1.13", "register-service-worker": "^1.7.2", "three": "^0.156.1", + "util": "^0.12.5", "vue": "^3.3.4", "vue-axios": "^3.5.2", "vue-facing-decorator": "^3.0.2", diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index 792eab2..167f211 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -12,6 +12,8 @@ export const SERVICE_ID = "endorser.ch"; export const CONTACT_URL_PREFIX = "https://endorser.ch"; // the suffix for the contact URL export const ENDORSER_JWT_URL_LOCATION = "/contact?jwt="; +// the prefix for handle IDs, the permanent ID for claims on Endorser +export const ENDORSER_CH_HANDLE_PREFIX = "https://endorser.ch/entity/"; export interface AgreeVerifiableCredential { "@context": string; @@ -42,10 +44,18 @@ export interface GenericClaim { "@context": string; "@type": string; issuedAt: string; + issuer: string; // "any" because arbitrary objects can be subject of agreement // eslint-disable-next-line @typescript-eslint/no-explicit-any claim: Record; } +export const BLANK_GENERIC_CLAIM: GenericClaim = { + "@context": SCHEMA_ORG_CONTEXT, + "@type": "", + issuedAt: "", + issuer: "", + claim: {}, +}; export interface GiveServerRecord { agentDid: string; @@ -143,6 +153,14 @@ export function isHiddenDid(did: string) { return did === HIDDEN_DID; } +export function stripEndorserPrefix(claimId: string) { + if (claimId && claimId.startsWith(ENDORSER_CH_HANDLE_PREFIX)) { + return claimId.substring(ENDORSER_CH_HANDLE_PREFIX.length); + } else { + return claimId; + } +} + /** always returns text, maybe UNNAMED_VISIBLE or UNKNOWN_ENTITY @@ -163,8 +181,8 @@ export function didInfo( return contact ? contact.name || "Contact With No Name" : isHiddenDid(did) - ? "Someone Not In Network" - : "Someone Not In Contacts"; + ? "Someone Not In Network" + : "Someone Not In Contacts"; } export interface ResultWithType { diff --git a/src/libs/util.ts b/src/libs/util.ts index 693a331..4a2dd28 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -1,3 +1,5 @@ +// many of these are also found in endorser-mobile utility.ts + export const isGlobalUri = (uri: string) => { return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/)); }; diff --git a/src/main.ts b/src/main.ts index 997d572..581fa26 100644 --- a/src/main.ts +++ b/src/main.ts @@ -43,6 +43,7 @@ import { faLongArrowAltLeft, faLongArrowAltRight, faMagnifyingGlass, + faMessage, faPen, faPersonCircleCheck, faPersonCircleQuestion, @@ -94,6 +95,7 @@ library.add( faLongArrowAltLeft, faLongArrowAltRight, faMagnifyingGlass, + faMessage, faPen, faPersonCircleCheck, faPersonCircleQuestion, diff --git a/src/router/index.ts b/src/router/index.ts index af3e32c..9dc2b5a 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -41,6 +41,12 @@ const routes: Array = [ import(/* webpackChunkName: "account" */ "../views/AccountViewView.vue"), beforeEnter: enterOrStart, }, + { + path: "/claim/:id?", + name: "claim", + component: () => + import(/* webpackChunkName: "claim" */ "../views/ClaimView.vue"), + }, { path: "/confirm-contact", name: "confirm-contact", diff --git a/src/util.d.ts b/src/util.d.ts new file mode 100644 index 0000000..ec32670 --- /dev/null +++ b/src/util.d.ts @@ -0,0 +1,2184 @@ +// from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/util.d.ts +/** + * The `node:util` module supports the needs of Node.js internal APIs. Many of the + * utilities are useful for application and module developers as well. To access + * it: + * + * ```js + * const util = require('node:util'); + * ``` + * @see [source](https://github.com/nodejs/node/blob/v20.2.0/lib/util.js) + */ +declare module "util" { + import * as types from "node:util/types"; + export interface InspectOptions { + /** + * If `true`, object's non-enumerable symbols and properties are included in the formatted result. + * `WeakMap` and `WeakSet` entries are also included as well as user defined prototype properties (excluding method properties). + * @default false + */ + showHidden?: boolean | undefined; + /** + * Specifies the number of times to recurse while formatting object. + * This is useful for inspecting large objects. + * To recurse up to the maximum call stack size pass `Infinity` or `null`. + * @default 2 + */ + depth?: number | null | undefined; + /** + * If `true`, the output is styled with ANSI color codes. Colors are customizable. + */ + colors?: boolean | undefined; + /** + * If `false`, `[util.inspect.custom](depth, opts, inspect)` functions are not invoked. + * @default true + */ + customInspect?: boolean | undefined; + /** + * If `true`, `Proxy` inspection includes the target and handler objects. + * @default false + */ + showProxy?: boolean | undefined; + /** + * Specifies the maximum number of `Array`, `TypedArray`, `WeakMap`, and `WeakSet` elements + * to include when formatting. Set to `null` or `Infinity` to show all elements. + * Set to `0` or negative to show no elements. + * @default 100 + */ + maxArrayLength?: number | null | undefined; + /** + * Specifies the maximum number of characters to + * include when formatting. Set to `null` or `Infinity` to show all elements. + * Set to `0` or negative to show no characters. + * @default 10000 + */ + maxStringLength?: number | null | undefined; + /** + * The length at which input values are split across multiple lines. + * Set to `Infinity` to format the input as a single line + * (in combination with `compact` set to `true` or any number >= `1`). + * @default 80 + */ + breakLength?: number | undefined; + /** + * Setting this to `false` causes each object key + * to be displayed on a new line. It will also add new lines to text that is + * longer than `breakLength`. If set to a number, the most `n` inner elements + * are united on a single line as long as all properties fit into + * `breakLength`. Short array elements are also grouped together. Note that no + * text will be reduced below 16 characters, no matter the `breakLength` size. + * For more information, see the example below. + * @default true + */ + compact?: boolean | number | undefined; + /** + * If set to `true` or a function, all properties of an object, and `Set` and `Map` + * entries are sorted in the resulting string. + * If set to `true` the default sort is used. + * If set to a function, it is used as a compare function. + */ + sorted?: boolean | ((a: string, b: string) => number) | undefined; + /** + * If set to `true`, getters are going to be + * inspected as well. If set to `'get'` only getters without setter are going + * to be inspected. If set to `'set'` only getters having a corresponding + * setter are going to be inspected. This might cause side effects depending on + * the getter function. + * @default false + */ + getters?: "get" | "set" | boolean | undefined; + /** + * If set to `true`, an underscore is used to separate every three digits in all bigints and numbers. + * @default false + */ + numericSeparator?: boolean | undefined; + } + export type Style = + | "special" + | "number" + | "bigint" + | "boolean" + | "undefined" + | "null" + | "string" + | "symbol" + | "date" + | "regexp" + | "module"; + export type CustomInspectFunction = (depth: number, options: InspectOptionsStylized) => any; // TODO: , inspect: inspect + export interface InspectOptionsStylized extends InspectOptions { + stylize(text: string, styleType: Style): string; + } + /** + * The `util.format()` method returns a formatted string using the first argument + * as a `printf`\-like format string which can contain zero or more format + * specifiers. Each specifier is replaced with the converted value from the + * corresponding argument. Supported specifiers are: + * + * If a specifier does not have a corresponding argument, it is not replaced: + * + * ```js + * util.format('%s:%s', 'foo'); + * // Returns: 'foo:%s' + * ``` + * + * Values that are not part of the format string are formatted using`util.inspect()` if their type is not `string`. + * + * If there are more arguments passed to the `util.format()` method than the + * number of specifiers, the extra arguments are concatenated to the returned + * string, separated by spaces: + * + * ```js + * util.format('%s:%s', 'foo', 'bar', 'baz'); + * // Returns: 'foo:bar baz' + * ``` + * + * If the first argument does not contain a valid format specifier, `util.format()`returns a string that is the concatenation of all arguments separated by spaces: + * + * ```js + * util.format(1, 2, 3); + * // Returns: '1 2 3' + * ``` + * + * If only one argument is passed to `util.format()`, it is returned as it is + * without any formatting: + * + * ```js + * util.format('%% %s'); + * // Returns: '%% %s' + * ``` + * + * `util.format()` is a synchronous method that is intended as a debugging tool. + * Some input values can have a significant performance overhead that can block the + * event loop. Use this function with care and never in a hot code path. + * @since v0.5.3 + * @param format A `printf`-like format string. + */ + export function format(format?: any, ...param: any[]): string; + /** + * This function is identical to {@link format}, except in that it takes + * an `inspectOptions` argument which specifies options that are passed along to {@link inspect}. + * + * ```js + * util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 }); + * // Returns 'See object { foo: 42 }', where `42` is colored as a number + * // when printed to a terminal. + * ``` + * @since v10.0.0 + */ + export function formatWithOptions(inspectOptions: InspectOptions, format?: any, ...param: any[]): string; + /** + * Returns the string name for a numeric error code that comes from a Node.js API. + * The mapping between error codes and error names is platform-dependent. + * See `Common System Errors` for the names of common errors. + * + * ```js + * fs.access('file/that/does/not/exist', (err) => { + * const name = util.getSystemErrorName(err.errno); + * console.error(name); // ENOENT + * }); + * ``` + * @since v9.7.0 + */ + export function getSystemErrorName(err: number): string; + /** + * Returns a Map of all system error codes available from the Node.js API. + * The mapping between error codes and error names is platform-dependent. + * See `Common System Errors` for the names of common errors. + * + * ```js + * fs.access('file/that/does/not/exist', (err) => { + * const errorMap = util.getSystemErrorMap(); + * const name = errorMap.get(err.errno); + * console.error(name); // ENOENT + * }); + * ``` + * @since v16.0.0, v14.17.0 + */ + export function getSystemErrorMap(): Map; + /** + * The `util.log()` method prints the given `string` to `stdout` with an included + * timestamp. + * + * ```js + * const util = require('node:util'); + * + * util.log('Timestamped message.'); + * ``` + * @since v0.3.0 + * @deprecated Since v6.0.0 - Use a third party module instead. + */ + export function log(string: string): void; + /** + * Returns the `string` after replacing any surrogate code points + * (or equivalently, any unpaired surrogate code units) with the + * Unicode "replacement character" U+FFFD. + * @since v16.8.0, v14.18.0 + */ + export function toUSVString(string: string): string; + /** + * Creates and returns an `AbortController` instance whose `AbortSignal` is marked + * as transferable and can be used with `structuredClone()` or `postMessage()`. + * @since v18.11.0 + * @experimental + * @returns A transferable AbortController + */ + export function transferableAbortController(): AbortController; + /** + * Marks the given `AbortSignal` as transferable so that it can be used with`structuredClone()` and `postMessage()`. + * + * ```js + * const signal = transferableAbortSignal(AbortSignal.timeout(100)); + * const channel = new MessageChannel(); + * channel.port2.postMessage(signal, [signal]); + * ``` + * @since v18.11.0 + * @experimental + * @param signal The AbortSignal + * @returns The same AbortSignal + */ + export function transferableAbortSignal(signal: AbortSignal): AbortSignal; + /** + * Listens to abort event on the provided `signal` and + * returns a promise that is fulfilled when the `signal` is + * aborted. If the passed `resource` is garbage collected before the `signal` is + * aborted, the returned promise shall remain pending indefinitely. + * + * ```js + * import { aborted } from 'node:util'; + * + * const dependent = obtainSomethingAbortable(); + * + * aborted(dependent.signal, dependent).then(() => { + * // Do something when dependent is aborted. + * }); + * + * dependent.on('event', () => { + * dependent.abort(); + * }); + * ``` + * @since v19.7.0 + * @experimental + * @param resource Any non-null entity, reference to which is held weakly. + */ + export function aborted(signal: AbortSignal, resource: any): Promise; + /** + * The `util.inspect()` method returns a string representation of `object` that is + * intended for debugging. The output of `util.inspect` may change at any time + * and should not be depended upon programmatically. Additional `options` may be + * passed that alter the result.`util.inspect()` will use the constructor's name and/or `@@toStringTag` to make + * an identifiable tag for an inspected value. + * + * ```js + * class Foo { + * get [Symbol.toStringTag]() { + * return 'bar'; + * } + * } + * + * class Bar {} + * + * const baz = Object.create(null, { [Symbol.toStringTag]: { value: 'foo' } }); + * + * util.inspect(new Foo()); // 'Foo [bar] {}' + * util.inspect(new Bar()); // 'Bar {}' + * util.inspect(baz); // '[foo] {}' + * ``` + * + * Circular references point to their anchor by using a reference index: + * + * ```js + * const { inspect } = require('node:util'); + * + * const obj = {}; + * obj.a = [obj]; + * obj.b = {}; + * obj.b.inner = obj.b; + * obj.b.obj = obj; + * + * console.log(inspect(obj)); + * // { + * // a: [ [Circular *1] ], + * // b: { inner: [Circular *2], obj: [Circular *1] } + * // } + * ``` + * + * The following example inspects all properties of the `util` object: + * + * ```js + * const util = require('node:util'); + * + * console.log(util.inspect(util, { showHidden: true, depth: null })); + * ``` + * + * The following example highlights the effect of the `compact` option: + * + * ```js + * const util = require('node:util'); + * + * const o = { + * a: [1, 2, [[ + * 'Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit, sed do ' + + * 'eiusmod \ntempor incididunt ut labore et dolore magna aliqua.', + * 'test', + * 'foo']], 4], + * b: new Map([['za', 1], ['zb', 'test']]), + * }; + * console.log(util.inspect(o, { compact: true, depth: 5, breakLength: 80 })); + * + * // { a: + * // [ 1, + * // 2, + * // [ [ 'Lorem ipsum dolor sit amet,\nconsectetur [...]', // A long line + * // 'test', + * // 'foo' ] ], + * // 4 ], + * // b: Map(2) { 'za' => 1, 'zb' => 'test' } } + * + * // Setting `compact` to false or an integer creates more reader friendly output. + * console.log(util.inspect(o, { compact: false, depth: 5, breakLength: 80 })); + * + * // { + * // a: [ + * // 1, + * // 2, + * // [ + * // [ + * // 'Lorem ipsum dolor sit amet,\n' + + * // 'consectetur adipiscing elit, sed do eiusmod \n' + + * // 'tempor incididunt ut labore et dolore magna aliqua.', + * // 'test', + * // 'foo' + * // ] + * // ], + * // 4 + * // ], + * // b: Map(2) { + * // 'za' => 1, + * // 'zb' => 'test' + * // } + * // } + * + * // Setting `breakLength` to e.g. 150 will print the "Lorem ipsum" text in a + * // single line. + * ``` + * + * The `showHidden` option allows [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and + * [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) entries to be + * inspected. If there are more entries than `maxArrayLength`, there is no + * guarantee which entries are displayed. That means retrieving the same [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) entries twice may + * result in different output. Furthermore, entries + * with no remaining strong references may be garbage collected at any time. + * + * ```js + * const { inspect } = require('node:util'); + * + * const obj = { a: 1 }; + * const obj2 = { b: 2 }; + * const weakSet = new WeakSet([obj, obj2]); + * + * console.log(inspect(weakSet, { showHidden: true })); + * // WeakSet { { a: 1 }, { b: 2 } } + * ``` + * + * The `sorted` option ensures that an object's property insertion order does not + * impact the result of `util.inspect()`. + * + * ```js + * const { inspect } = require('node:util'); + * const assert = require('node:assert'); + * + * const o1 = { + * b: [2, 3, 1], + * a: '`a` comes before `b`', + * c: new Set([2, 3, 1]), + * }; + * console.log(inspect(o1, { sorted: true })); + * // { a: '`a` comes before `b`', b: [ 2, 3, 1 ], c: Set(3) { 1, 2, 3 } } + * console.log(inspect(o1, { sorted: (a, b) => b.localeCompare(a) })); + * // { c: Set(3) { 3, 2, 1 }, b: [ 2, 3, 1 ], a: '`a` comes before `b`' } + * + * const o2 = { + * c: new Set([2, 1, 3]), + * a: '`a` comes before `b`', + * b: [2, 3, 1], + * }; + * assert.strict.equal( + * inspect(o1, { sorted: true }), + * inspect(o2, { sorted: true }), + * ); + * ``` + * + * The `numericSeparator` option adds an underscore every three digits to all + * numbers. + * + * ```js + * const { inspect } = require('node:util'); + * + * const thousand = 1_000; + * const million = 1_000_000; + * const bigNumber = 123_456_789n; + * const bigDecimal = 1_234.123_45; + * + * console.log(inspect(thousand, { numericSeparator: true })); + * // 1_000 + * console.log(inspect(million, { numericSeparator: true })); + * // 1_000_000 + * console.log(inspect(bigNumber, { numericSeparator: true })); + * // 123_456_789n + * console.log(inspect(bigDecimal, { numericSeparator: true })); + * // 1_234.123_45 + * ``` + * + * `util.inspect()` is a synchronous method intended for debugging. Its maximum + * output length is approximately 128 MiB. Inputs that result in longer output will + * be truncated. + * @since v0.3.0 + * @param object Any JavaScript primitive or `Object`. + * @return The representation of `object`. + */ + export function inspect(object: any, showHidden?: boolean, depth?: number | null, color?: boolean): string; + export function inspect(object: any, options?: InspectOptions): string; + export namespace inspect { + let colors: NodeJS.Dict<[number, number]>; + let styles: { + [K in Style]: string; + }; + let defaultOptions: InspectOptions; + /** + * Allows changing inspect settings from the repl. + */ + let replDefaults: InspectOptions; + /** + * That can be used to declare custom inspect functions. + */ + const custom: unique symbol; + } + /** + * Alias for [`Array.isArray()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray). + * + * Returns `true` if the given `object` is an `Array`. Otherwise, returns `false`. + * + * ```js + * const util = require('node:util'); + * + * util.isArray([]); + * // Returns: true + * util.isArray(new Array()); + * // Returns: true + * util.isArray({}); + * // Returns: false + * ``` + * @since v0.6.0 + * @deprecated Since v4.0.0 - Use `isArray` instead. + */ + export function isArray(object: unknown): object is unknown[]; + /** + * Returns `true` if the given `object` is a `RegExp`. Otherwise, returns `false`. + * + * ```js + * const util = require('node:util'); + * + * util.isRegExp(/some regexp/); + * // Returns: true + * util.isRegExp(new RegExp('another regexp')); + * // Returns: true + * util.isRegExp({}); + * // Returns: false + * ``` + * @since v0.6.0 + * @deprecated Since v4.0.0 - Deprecated + */ + export function isRegExp(object: unknown): object is RegExp; + /** + * Returns `true` if the given `object` is a `Date`. Otherwise, returns `false`. + * + * ```js + * const util = require('node:util'); + * + * util.isDate(new Date()); + * // Returns: true + * util.isDate(Date()); + * // false (without 'new' returns a String) + * util.isDate({}); + * // Returns: false + * ``` + * @since v0.6.0 + * @deprecated Since v4.0.0 - Use {@link types.isDate} instead. + */ + export function isDate(object: unknown): object is Date; + /** + * Returns `true` if the given `object` is an `Error`. Otherwise, returns`false`. + * + * ```js + * const util = require('node:util'); + * + * util.isError(new Error()); + * // Returns: true + * util.isError(new TypeError()); + * // Returns: true + * util.isError({ name: 'Error', message: 'an error occurred' }); + * // Returns: false + * ``` + * + * This method relies on `Object.prototype.toString()` behavior. It is + * possible to obtain an incorrect result when the `object` argument manipulates`@@toStringTag`. + * + * ```js + * const util = require('node:util'); + * const obj = { name: 'Error', message: 'an error occurred' }; + * + * util.isError(obj); + * // Returns: false + * obj[Symbol.toStringTag] = 'Error'; + * util.isError(obj); + * // Returns: true + * ``` + * @since v0.6.0 + * @deprecated Since v4.0.0 - Use {@link types.isNativeError} instead. + */ + export function isError(object: unknown): object is Error; + /** + * Usage of `util.inherits()` is discouraged. Please use the ES6 `class` and`extends` keywords to get language level inheritance support. Also note + * that the two styles are [semantically incompatible](https://github.com/nodejs/node/issues/4179). + * + * Inherit the prototype methods from one [constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor) into another. The + * prototype of `constructor` will be set to a new object created from`superConstructor`. + * + * This mainly adds some input validation on top of`Object.setPrototypeOf(constructor.prototype, superConstructor.prototype)`. + * As an additional convenience, `superConstructor` will be accessible + * through the `constructor.super_` property. + * + * ```js + * const util = require('node:util'); + * const EventEmitter = require('node:events'); + * + * function MyStream() { + * EventEmitter.call(this); + * } + * + * util.inherits(MyStream, EventEmitter); + * + * MyStream.prototype.write = function(data) { + * this.emit('data', data); + * }; + * + * const stream = new MyStream(); + * + * console.log(stream instanceof EventEmitter); // true + * console.log(MyStream.super_ === EventEmitter); // true + * + * stream.on('data', (data) => { + * console.log(`Received data: "${data}"`); + * }); + * stream.write('It works!'); // Received data: "It works!" + * ``` + * + * ES6 example using `class` and `extends`: + * + * ```js + * const EventEmitter = require('node:events'); + * + * class MyStream extends EventEmitter { + * write(data) { + * this.emit('data', data); + * } + * } + * + * const stream = new MyStream(); + * + * stream.on('data', (data) => { + * console.log(`Received data: "${data}"`); + * }); + * stream.write('With ES6'); + * ``` + * @since v0.3.0 + * @legacy Use ES2015 class syntax and `extends` keyword instead. + */ + export function inherits(constructor: unknown, superConstructor: unknown): void; + export type DebugLoggerFunction = (msg: string, ...param: unknown[]) => void; + export interface DebugLogger extends DebugLoggerFunction { + enabled: boolean; + } + /** + * The `util.debuglog()` method is used to create a function that conditionally + * writes debug messages to `stderr` based on the existence of the `NODE_DEBUG`environment variable. If the `section` name appears within the value of that + * environment variable, then the returned function operates similar to `console.error()`. If not, then the returned function is a no-op. + * + * ```js + * const util = require('node:util'); + * const debuglog = util.debuglog('foo'); + * + * debuglog('hello from foo [%d]', 123); + * ``` + * + * If this program is run with `NODE_DEBUG=foo` in the environment, then + * it will output something like: + * + * ```console + * FOO 3245: hello from foo [123] + * ``` + * + * where `3245` is the process id. If it is not run with that + * environment variable set, then it will not print anything. + * + * The `section` supports wildcard also: + * + * ```js + * const util = require('node:util'); + * const debuglog = util.debuglog('foo-bar'); + * + * debuglog('hi there, it\'s foo-bar [%d]', 2333); + * ``` + * + * if it is run with `NODE_DEBUG=foo*` in the environment, then it will output + * something like: + * + * ```console + * FOO-BAR 3257: hi there, it's foo-bar [2333] + * ``` + * + * Multiple comma-separated `section` names may be specified in the `NODE_DEBUG`environment variable: `NODE_DEBUG=fs,net,tls`. + * + * The optional `callback` argument can be used to replace the logging function + * with a different function that doesn't have any initialization or + * unnecessary wrapping. + * + * ```js + * const util = require('node:util'); + * let debuglog = util.debuglog('internals', (debug) => { + * // Replace with a logging function that optimizes out + * // testing if the section is enabled + * debuglog = debug; + * }); + * ``` + * @since v0.11.3 + * @param section A string identifying the portion of the application for which the `debuglog` function is being created. + * @param callback A callback invoked the first time the logging function is called with a function argument that is a more optimized logging function. + * @return The logging function + */ + export function debuglog(section: string, callback?: (fn: DebugLoggerFunction) => void): DebugLogger; + export const debug: typeof debuglog; + /** + * Returns `true` if the given `object` is a `Boolean`. Otherwise, returns `false`. + * + * ```js + * const util = require('node:util'); + * + * util.isBoolean(1); + * // Returns: false + * util.isBoolean(0); + * // Returns: false + * util.isBoolean(false); + * // Returns: true + * ``` + * @since v0.11.5 + * @deprecated Since v4.0.0 - Use `typeof value === 'boolean'` instead. + */ + export function isBoolean(object: unknown): object is boolean; + /** + * Returns `true` if the given `object` is a `Buffer`. Otherwise, returns `false`. + * + * ```js + * const util = require('node:util'); + * + * util.isBuffer({ length: 0 }); + * // Returns: false + * util.isBuffer([]); + * // Returns: false + * util.isBuffer(Buffer.from('hello world')); + * // Returns: true + * ``` + * @since v0.11.5 + * @deprecated Since v4.0.0 - Use `isBuffer` instead. + */ + export function isBuffer(object: unknown): object is Buffer; + /** + * Returns `true` if the given `object` is a `Function`. Otherwise, returns`false`. + * + * ```js + * const util = require('node:util'); + * + * function Foo() {} + * const Bar = () => {}; + * + * util.isFunction({}); + * // Returns: false + * util.isFunction(Foo); + * // Returns: true + * util.isFunction(Bar); + * // Returns: true + * ``` + * @since v0.11.5 + * @deprecated Since v4.0.0 - Use `typeof value === 'function'` instead. + */ + export function isFunction(object: unknown): boolean; + /** + * Returns `true` if the given `object` is strictly `null`. Otherwise, returns`false`. + * + * ```js + * const util = require('node:util'); + * + * util.isNull(0); + * // Returns: false + * util.isNull(undefined); + * // Returns: false + * util.isNull(null); + * // Returns: true + * ``` + * @since v0.11.5 + * @deprecated Since v4.0.0 - Use `value === null` instead. + */ + export function isNull(object: unknown): object is null; + /** + * Returns `true` if the given `object` is `null` or `undefined`. Otherwise, + * returns `false`. + * + * ```js + * const util = require('node:util'); + * + * util.isNullOrUndefined(0); + * // Returns: false + * util.isNullOrUndefined(undefined); + * // Returns: true + * util.isNullOrUndefined(null); + * // Returns: true + * ``` + * @since v0.11.5 + * @deprecated Since v4.0.0 - Use `value === undefined || value === null` instead. + */ + export function isNullOrUndefined(object: unknown): object is null | undefined; + /** + * Returns `true` if the given `object` is a `Number`. Otherwise, returns `false`. + * + * ```js + * const util = require('node:util'); + * + * util.isNumber(false); + * // Returns: false + * util.isNumber(Infinity); + * // Returns: true + * util.isNumber(0); + * // Returns: true + * util.isNumber(NaN); + * // Returns: true + * ``` + * @since v0.11.5 + * @deprecated Since v4.0.0 - Use `typeof value === 'number'` instead. + */ + export function isNumber(object: unknown): object is number; + /** + * Returns `true` if the given `object` is strictly an `Object`**and** not a`Function` (even though functions are objects in JavaScript). + * Otherwise, returns `false`. + * + * ```js + * const util = require('node:util'); + * + * util.isObject(5); + * // Returns: false + * util.isObject(null); + * // Returns: false + * util.isObject({}); + * // Returns: true + * util.isObject(() => {}); + * // Returns: false + * ``` + * @since v0.11.5 + * @deprecated Since v4.0.0 - Use `value !== null && typeof value === 'object'` instead. + */ + export function isObject(object: unknown): boolean; + /** + * Returns `true` if the given `object` is a primitive type. Otherwise, returns`false`. + * + * ```js + * const util = require('node:util'); + * + * util.isPrimitive(5); + * // Returns: true + * util.isPrimitive('foo'); + * // Returns: true + * util.isPrimitive(false); + * // Returns: true + * util.isPrimitive(null); + * // Returns: true + * util.isPrimitive(undefined); + * // Returns: true + * util.isPrimitive({}); + * // Returns: false + * util.isPrimitive(() => {}); + * // Returns: false + * util.isPrimitive(/^$/); + * // Returns: false + * util.isPrimitive(new Date()); + * // Returns: false + * ``` + * @since v0.11.5 + * @deprecated Since v4.0.0 - Use `(typeof value !== 'object' && typeof value !== 'function') || value === null` instead. + */ + export function isPrimitive(object: unknown): boolean; + /** + * Returns `true` if the given `object` is a `string`. Otherwise, returns `false`. + * + * ```js + * const util = require('node:util'); + * + * util.isString(''); + * // Returns: true + * util.isString('foo'); + * // Returns: true + * util.isString(String('foo')); + * // Returns: true + * util.isString(5); + * // Returns: false + * ``` + * @since v0.11.5 + * @deprecated Since v4.0.0 - Use `typeof value === 'string'` instead. + */ + export function isString(object: unknown): object is string; + /** + * Returns `true` if the given `object` is a `Symbol`. Otherwise, returns `false`. + * + * ```js + * const util = require('node:util'); + * + * util.isSymbol(5); + * // Returns: false + * util.isSymbol('foo'); + * // Returns: false + * util.isSymbol(Symbol('foo')); + * // Returns: true + * ``` + * @since v0.11.5 + * @deprecated Since v4.0.0 - Use `typeof value === 'symbol'` instead. + */ + export function isSymbol(object: unknown): object is symbol; + /** + * Returns `true` if the given `object` is `undefined`. Otherwise, returns `false`. + * + * ```js + * const util = require('node:util'); + * + * const foo = undefined; + * util.isUndefined(5); + * // Returns: false + * util.isUndefined(foo); + * // Returns: true + * util.isUndefined(null); + * // Returns: false + * ``` + * @since v0.11.5 + * @deprecated Since v4.0.0 - Use `value === undefined` instead. + */ + export function isUndefined(object: unknown): object is undefined; + /** + * The `util.deprecate()` method wraps `fn` (which may be a function or class) in + * such a way that it is marked as deprecated. + * + * ```js + * const util = require('node:util'); + * + * exports.obsoleteFunction = util.deprecate(() => { + * // Do something here. + * }, 'obsoleteFunction() is deprecated. Use newShinyFunction() instead.'); + * ``` + * + * When called, `util.deprecate()` will return a function that will emit a`DeprecationWarning` using the `'warning'` event. The warning will + * be emitted and printed to `stderr` the first time the returned function is + * called. After the warning is emitted, the wrapped function is called without + * emitting a warning. + * + * If the same optional `code` is supplied in multiple calls to `util.deprecate()`, + * the warning will be emitted only once for that `code`. + * + * ```js + * const util = require('node:util'); + * + * const fn1 = util.deprecate(someFunction, someMessage, 'DEP0001'); + * const fn2 = util.deprecate(someOtherFunction, someOtherMessage, 'DEP0001'); + * fn1(); // Emits a deprecation warning with code DEP0001 + * fn2(); // Does not emit a deprecation warning because it has the same code + * ``` + * + * If either the `--no-deprecation` or `--no-warnings` command-line flags are + * used, or if the `process.noDeprecation` property is set to `true`_prior_ to + * the first deprecation warning, the `util.deprecate()` method does nothing. + * + * If the `--trace-deprecation` or `--trace-warnings` command-line flags are set, + * or the `process.traceDeprecation` property is set to `true`, a warning and a + * stack trace are printed to `stderr` the first time the deprecated function is + * called. + * + * If the `--throw-deprecation` command-line flag is set, or the`process.throwDeprecation` property is set to `true`, then an exception will be + * thrown when the deprecated function is called. + * + * The `--throw-deprecation` command-line flag and `process.throwDeprecation`property take precedence over `--trace-deprecation` and`process.traceDeprecation`. + * @since v0.8.0 + * @param fn The function that is being deprecated. + * @param msg A warning message to display when the deprecated function is invoked. + * @param code A deprecation code. See the `list of deprecated APIs` for a list of codes. + * @return The deprecated function wrapped to emit a warning. + */ + export function deprecate(fn: T, msg: string, code?: string): T; + /** + * Returns `true` if there is deep strict equality between `val1` and `val2`. + * Otherwise, returns `false`. + * + * See `assert.deepStrictEqual()` for more information about deep strict + * equality. + * @since v9.0.0 + */ + export function isDeepStrictEqual(val1: unknown, val2: unknown): boolean; + /** + * Returns `str` with any ANSI escape codes removed. + * + * ```js + * console.log(util.stripVTControlCharacters('\u001B[4mvalue\u001B[0m')); + * // Prints "value" + * ``` + * @since v16.11.0 + */ + export function stripVTControlCharacters(str: string): string; + /** + * Takes an `async` function (or a function that returns a `Promise`) and returns a + * function following the error-first callback style, i.e. taking + * an `(err, value) => ...` callback as the last argument. In the callback, the + * first argument will be the rejection reason (or `null` if the `Promise`resolved), and the second argument will be the resolved value. + * + * ```js + * const util = require('node:util'); + * + * async function fn() { + * return 'hello world'; + * } + * const callbackFunction = util.callbackify(fn); + * + * callbackFunction((err, ret) => { + * if (err) throw err; + * console.log(ret); + * }); + * ``` + * + * Will print: + * + * ```text + * hello world + * ``` + * + * The callback is executed asynchronously, and will have a limited stack trace. + * If the callback throws, the process will emit an `'uncaughtException'` event, and if not handled will exit. + * + * Since `null` has a special meaning as the first argument to a callback, if a + * wrapped function rejects a `Promise` with a falsy value as a reason, the value + * is wrapped in an `Error` with the original value stored in a field named`reason`. + * + * ```js + * function fn() { + * return Promise.reject(null); + * } + * const callbackFunction = util.callbackify(fn); + * + * callbackFunction((err, ret) => { + * // When the Promise was rejected with `null` it is wrapped with an Error and + * // the original value is stored in `reason`. + * err && Object.hasOwn(err, 'reason') && err.reason === null; // true + * }); + * ``` + * @since v8.2.0 + * @param fn An `async` function + * @return a callback style function + */ + export function callbackify(fn: () => Promise): (callback: (err: NodeJS.ErrnoException) => void) => void; + export function callbackify( + fn: () => Promise, + ): (callback: (err: NodeJS.ErrnoException, result: TResult) => void) => void; + export function callbackify( + fn: (arg1: T1) => Promise, + ): (arg1: T1, callback: (err: NodeJS.ErrnoException) => void) => void; + export function callbackify( + fn: (arg1: T1) => Promise, + ): (arg1: T1, callback: (err: NodeJS.ErrnoException, result: TResult) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2) => Promise, + ): (arg1: T1, arg2: T2, callback: (err: NodeJS.ErrnoException) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2) => Promise, + ): (arg1: T1, arg2: T2, callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3) => Promise, + ): (arg1: T1, arg2: T2, arg3: T3, callback: (err: NodeJS.ErrnoException) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3) => Promise, + ): (arg1: T1, arg2: T2, arg3: T3, callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise, + ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, callback: (err: NodeJS.ErrnoException) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise, + ): ( + arg1: T1, + arg2: T2, + arg3: T3, + arg4: T4, + callback: (err: NodeJS.ErrnoException | null, result: TResult) => void, + ) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise, + ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, callback: (err: NodeJS.ErrnoException) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise, + ): ( + arg1: T1, + arg2: T2, + arg3: T3, + arg4: T4, + arg5: T5, + callback: (err: NodeJS.ErrnoException | null, result: TResult) => void, + ) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6) => Promise, + ): ( + arg1: T1, + arg2: T2, + arg3: T3, + arg4: T4, + arg5: T5, + arg6: T6, + callback: (err: NodeJS.ErrnoException) => void, + ) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6) => Promise, + ): ( + arg1: T1, + arg2: T2, + arg3: T3, + arg4: T4, + arg5: T5, + arg6: T6, + callback: (err: NodeJS.ErrnoException | null, result: TResult) => void, + ) => void; + export interface CustomPromisifyLegacy extends Function { + __promisify__: TCustom; + } + export interface CustomPromisifySymbol extends Function { + [promisify.custom]: TCustom; + } + export type CustomPromisify = + | CustomPromisifySymbol + | CustomPromisifyLegacy; + /** + * Takes a function following the common error-first callback style, i.e. taking + * an `(err, value) => ...` callback as the last argument, and returns a version + * that returns promises. + * + * ```js + * const util = require('node:util'); + * const fs = require('node:fs'); + * + * const stat = util.promisify(fs.stat); + * stat('.').then((stats) => { + * // Do something with `stats` + * }).catch((error) => { + * // Handle the error. + * }); + * ``` + * + * Or, equivalently using `async function`s: + * + * ```js + * const util = require('node:util'); + * const fs = require('node:fs'); + * + * const stat = util.promisify(fs.stat); + * + * async function callStat() { + * const stats = await stat('.'); + * console.log(`This directory is owned by ${stats.uid}`); + * } + * + * callStat(); + * ``` + * + * If there is an `original[util.promisify.custom]` property present, `promisify`will return its value, see `Custom promisified functions`. + * + * `promisify()` assumes that `original` is a function taking a callback as its + * final argument in all cases. If `original` is not a function, `promisify()`will throw an error. If `original` is a function but its last argument is not + * an error-first callback, it will still be passed an error-first + * callback as its last argument. + * + * Using `promisify()` on class methods or other methods that use `this` may not + * work as expected unless handled specially: + * + * ```js + * const util = require('node:util'); + * + * class Foo { + * constructor() { + * this.a = 42; + * } + * + * bar(callback) { + * callback(null, this.a); + * } + * } + * + * const foo = new Foo(); + * + * const naiveBar = util.promisify(foo.bar); + * // TypeError: Cannot read property 'a' of undefined + * // naiveBar().then(a => console.log(a)); + * + * naiveBar.call(foo).then((a) => console.log(a)); // '42' + * + * const bindBar = naiveBar.bind(foo); + * bindBar().then((a) => console.log(a)); // '42' + * ``` + * @since v8.0.0 + */ + export function promisify(fn: CustomPromisify): TCustom; + export function promisify( + fn: (callback: (err: any, result: TResult) => void) => void, + ): () => Promise; + export function promisify(fn: (callback: (err?: any) => void) => void): () => Promise; + export function promisify( + fn: (arg1: T1, callback: (err: any, result: TResult) => void) => void, + ): (arg1: T1) => Promise; + export function promisify(fn: (arg1: T1, callback: (err?: any) => void) => void): (arg1: T1) => Promise; + export function promisify( + fn: (arg1: T1, arg2: T2, callback: (err: any, result: TResult) => void) => void, + ): (arg1: T1, arg2: T2) => Promise; + export function promisify( + fn: (arg1: T1, arg2: T2, callback: (err?: any) => void) => void, + ): (arg1: T1, arg2: T2) => Promise; + export function promisify( + fn: (arg1: T1, arg2: T2, arg3: T3, callback: (err: any, result: TResult) => void) => void, + ): (arg1: T1, arg2: T2, arg3: T3) => Promise; + export function promisify( + fn: (arg1: T1, arg2: T2, arg3: T3, callback: (err?: any) => void) => void, + ): (arg1: T1, arg2: T2, arg3: T3) => Promise; + export function promisify( + fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, callback: (err: any, result: TResult) => void) => void, + ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise; + export function promisify( + fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, callback: (err?: any) => void) => void, + ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise; + export function promisify( + fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, callback: (err: any, result: TResult) => void) => void, + ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise; + export function promisify( + fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, callback: (err?: any) => void) => void, + ): (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise; + export function promisify(fn: Function): Function; + export namespace promisify { + /** + * That can be used to declare custom promisified variants of functions. + */ + const custom: unique symbol; + } + /** + * An implementation of the [WHATWG Encoding Standard](https://encoding.spec.whatwg.org/) `TextDecoder` API. + * + * ```js + * const decoder = new TextDecoder(); + * const u8arr = new Uint8Array([72, 101, 108, 108, 111]); + * console.log(decoder.decode(u8arr)); // Hello + * ``` + * @since v8.3.0 + */ + export class TextDecoder { + /** + * The encoding supported by the `TextDecoder` instance. + */ + readonly encoding: string; + /** + * The value will be `true` if decoding errors result in a `TypeError` being + * thrown. + */ + readonly fatal: boolean; + /** + * The value will be `true` if the decoding result will include the byte order + * mark. + */ + readonly ignoreBOM: boolean; + constructor( + encoding?: string, + options?: { + fatal?: boolean | undefined; + ignoreBOM?: boolean | undefined; + }, + ); + /** + * Decodes the `input` and returns a string. If `options.stream` is `true`, any + * incomplete byte sequences occurring at the end of the `input` are buffered + * internally and emitted after the next call to `textDecoder.decode()`. + * + * If `textDecoder.fatal` is `true`, decoding errors that occur will result in a`TypeError` being thrown. + * @param input An `ArrayBuffer`, `DataView`, or `TypedArray` instance containing the encoded data. + */ + decode( + input?: NodeJS.ArrayBufferView | ArrayBuffer | null, + options?: { + stream?: boolean | undefined; + }, + ): string; + } + export interface EncodeIntoResult { + /** + * The read Unicode code units of input. + */ + read: number; + /** + * The written UTF-8 bytes of output. + */ + written: number; + } + export { types }; + + //// TextEncoder/Decoder + /** + * An implementation of the [WHATWG Encoding Standard](https://encoding.spec.whatwg.org/) `TextEncoder` API. All + * instances of `TextEncoder` only support UTF-8 encoding. + * + * ```js + * const encoder = new TextEncoder(); + * const uint8array = encoder.encode('this is some data'); + * ``` + * + * The `TextEncoder` class is also available on the global object. + * @since v8.3.0 + */ + export class TextEncoder { + /** + * The encoding supported by the `TextEncoder` instance. Always set to `'utf-8'`. + */ + readonly encoding: string; + /** + * UTF-8 encodes the `input` string and returns a `Uint8Array` containing the + * encoded bytes. + * @param [input='an empty string'] The text to encode. + */ + encode(input?: string): Uint8Array; + /** + * UTF-8 encodes the `src` string to the `dest` Uint8Array and returns an object + * containing the read Unicode code units and written UTF-8 bytes. + * + * ```js + * const encoder = new TextEncoder(); + * const src = 'this is some data'; + * const dest = new Uint8Array(10); + * const { read, written } = encoder.encodeInto(src, dest); + * ``` + * @param src The text to encode. + * @param dest The array to hold the encode result. + */ + encodeInto(src: string, dest: Uint8Array): EncodeIntoResult; + } + import { TextDecoder as _TextDecoder, TextEncoder as _TextEncoder } from "util"; + global { + /** + * `TextDecoder` class is a global reference for `require('util').TextDecoder` + * https://nodejs.org/api/globals.html#textdecoder + * @since v11.0.0 + */ + var TextDecoder: typeof globalThis extends { + onmessage: any; + TextDecoder: infer TextDecoder; + } ? TextDecoder + : typeof _TextDecoder; + /** + * `TextEncoder` class is a global reference for `require('util').TextEncoder` + * https://nodejs.org/api/globals.html#textencoder + * @since v11.0.0 + */ + var TextEncoder: typeof globalThis extends { + onmessage: any; + TextEncoder: infer TextEncoder; + } ? TextEncoder + : typeof _TextEncoder; + } + + //// parseArgs + /** + * Provides a higher level API for command-line argument parsing than interacting + * with `process.argv` directly. Takes a specification for the expected arguments + * and returns a structured object with the parsed options and positionals. + * + * ```js + * import { parseArgs } from 'node:util'; + * const args = ['-f', '--bar', 'b']; + * const options = { + * foo: { + * type: 'boolean', + * short: 'f', + * }, + * bar: { + * type: 'string', + * }, + * }; + * const { + * values, + * positionals, + * } = parseArgs({ args, options }); + * console.log(values, positionals); + * // Prints: [Object: null prototype] { foo: true, bar: 'b' } [] + * ``` + * @since v18.3.0, v16.17.0 + * @param config Used to provide arguments for parsing and to configure the parser. `config` supports the following properties: + * @return The parsed command line arguments: + */ + export function parseArgs(config?: T): ParsedResults; + interface ParseArgsOptionConfig { + /** + * Type of argument. + */ + type: "string" | "boolean"; + /** + * Whether this option can be provided multiple times. + * If `true`, all values will be collected in an array. + * If `false`, values for the option are last-wins. + * @default false. + */ + multiple?: boolean | undefined; + /** + * A single character alias for the option. + */ + short?: string | undefined; + /** + * The default option value when it is not set by args. + * It must be of the same type as the the `type` property. + * When `multiple` is `true`, it must be an array. + * @since v18.11.0 + */ + default?: string | boolean | string[] | boolean[] | undefined; + } + interface ParseArgsOptionsConfig { + [longOption: string]: ParseArgsOptionConfig; + } + export interface ParseArgsConfig { + /** + * Array of argument strings. + */ + args?: string[] | undefined; + /** + * Used to describe arguments known to the parser. + */ + options?: ParseArgsOptionsConfig | undefined; + /** + * Should an error be thrown when unknown arguments are encountered, + * or when arguments are passed that do not match the `type` configured in `options`. + * @default true + */ + strict?: boolean | undefined; + /** + * Whether this command accepts positional arguments. + */ + allowPositionals?: boolean | undefined; + /** + * Return the parsed tokens. This is useful for extending the built-in behavior, + * from adding additional checks through to reprocessing the tokens in different ways. + * @default false + */ + tokens?: boolean | undefined; + } + /* + IfDefaultsTrue and IfDefaultsFalse are helpers to handle default values for missing boolean properties. + TypeScript does not have exact types for objects: https://github.com/microsoft/TypeScript/issues/12936 + This means it is impossible to distinguish between "field X is definitely not present" and "field X may or may not be present". + But we expect users to generally provide their config inline or `as const`, which means TS will always know whether a given field is present. + So this helper treats "not definitely present" (i.e., not `extends boolean`) as being "definitely not present", i.e. it should have its default value. + This is technically incorrect but is a much nicer UX for the common case. + The IfDefaultsTrue version is for things which default to true; the IfDefaultsFalse version is for things which default to false. + */ + type IfDefaultsTrue = T extends true ? IfTrue + : T extends false ? IfFalse + : IfTrue; + + // we put the `extends false` condition first here because `undefined` compares like `any` when `strictNullChecks: false` + type IfDefaultsFalse = T extends false ? IfFalse + : T extends true ? IfTrue + : IfFalse; + + type ExtractOptionValue = IfDefaultsTrue< + T["strict"], + O["type"] extends "string" ? string : O["type"] extends "boolean" ? boolean : string | boolean, + string | boolean + >; + + type ParsedValues = + & IfDefaultsTrue + & (T["options"] extends ParseArgsOptionsConfig ? { + -readonly [LongOption in keyof T["options"]]: IfDefaultsFalse< + T["options"][LongOption]["multiple"], + undefined | Array>, + undefined | ExtractOptionValue + >; + } + : {}); + + type ParsedPositionals = IfDefaultsTrue< + T["strict"], + IfDefaultsFalse, + IfDefaultsTrue + >; + + type PreciseTokenForOptions< + K extends string, + O extends ParseArgsOptionConfig, + > = O["type"] extends "string" ? { + kind: "option"; + index: number; + name: K; + rawName: string; + value: string; + inlineValue: boolean; + } + : O["type"] extends "boolean" ? { + kind: "option"; + index: number; + name: K; + rawName: string; + value: undefined; + inlineValue: undefined; + } + : OptionToken & { name: K }; + + type TokenForOptions< + T extends ParseArgsConfig, + K extends keyof T["options"] = keyof T["options"], + > = K extends unknown + ? T["options"] extends ParseArgsOptionsConfig ? PreciseTokenForOptions + : OptionToken + : never; + + type ParsedOptionToken = IfDefaultsTrue, OptionToken>; + + type ParsedPositionalToken = IfDefaultsTrue< + T["strict"], + IfDefaultsFalse, + IfDefaultsTrue + >; + + type ParsedTokens = Array< + ParsedOptionToken | ParsedPositionalToken | { kind: "option-terminator"; index: number } + >; + + type PreciseParsedResults = IfDefaultsFalse< + T["tokens"], + { + values: ParsedValues; + positionals: ParsedPositionals; + tokens: ParsedTokens; + }, + { + values: ParsedValues; + positionals: ParsedPositionals; + } + >; + + type OptionToken = + | { kind: "option"; index: number; name: string; rawName: string; value: string; inlineValue: boolean } + | { + kind: "option"; + index: number; + name: string; + rawName: string; + value: undefined; + inlineValue: undefined; + }; + + type Token = + | OptionToken + | { kind: "positional"; index: number; value: string } + | { kind: "option-terminator"; index: number }; + + // If ParseArgsConfig extends T, then the user passed config constructed elsewhere. + // So we can't rely on the `"not definitely present" implies "definitely not present"` assumption mentioned above. + type ParsedResults = ParseArgsConfig extends T ? { + values: { + [longOption: string]: undefined | string | boolean | Array; + }; + positionals: string[]; + tokens?: Token[]; + } + : PreciseParsedResults; + + /** + * An implementation of [the MIMEType class](https://bmeck.github.io/node-proposal-mime-api/). + * + * In accordance with browser conventions, all properties of `MIMEType` objects + * are implemented as getters and setters on the class prototype, rather than as + * data properties on the object itself. + * + * A MIME string is a structured string containing multiple meaningful + * components. When parsed, a `MIMEType` object is returned containing + * properties for each of these components. + * @since v19.1.0, v18.13.0 + * @experimental + */ + export class MIMEType { + /** + * Creates a new MIMEType object by parsing the input. + * + * A `TypeError` will be thrown if the `input` is not a valid MIME. + * Note that an effort will be made to coerce the given values into strings. + * @param input The input MIME to parse. + */ + constructor(input: string | { toString: () => string }); + + /** + * Gets and sets the type portion of the MIME. + * + * ```js + * import { MIMEType } from 'node:util'; + * + * const myMIME = new MIMEType('text/javascript'); + * console.log(myMIME.type); + * // Prints: text + * myMIME.type = 'application'; + * console.log(myMIME.type); + * // Prints: application + * console.log(String(myMIME)); + * // Prints: application/javascript + * ``` + */ + type: string; + /** + * Gets and sets the subtype portion of the MIME. + * + * ```js + * import { MIMEType } from 'node:util'; + * + * const myMIME = new MIMEType('text/ecmascript'); + * console.log(myMIME.subtype); + * // Prints: ecmascript + * myMIME.subtype = 'javascript'; + * console.log(myMIME.subtype); + * // Prints: javascript + * console.log(String(myMIME)); + * // Prints: text/javascript + * ``` + */ + subtype: string; + /** + * Gets the essence of the MIME. This property is read only. + * Use `mime.type` or `mime.subtype` to alter the MIME. + * + * ```js + * import { MIMEType } from 'node:util'; + * + * const myMIME = new MIMEType('text/javascript;key=value'); + * console.log(myMIME.essence); + * // Prints: text/javascript + * myMIME.type = 'application'; + * console.log(myMIME.essence); + * // Prints: application/javascript + * console.log(String(myMIME)); + * // Prints: application/javascript;key=value + * ``` + */ + readonly essence: string; + /** + * Gets the `MIMEParams` object representing the + * parameters of the MIME. This property is read-only. See `MIMEParams` documentation for details. + */ + readonly params: MIMEParams; + /** + * The `toString()` method on the `MIMEType` object returns the serialized MIME. + * + * Because of the need for standard compliance, this method does not allow users + * to customize the serialization process of the MIME. + */ + toString(): string; + } + /** + * The `MIMEParams` API provides read and write access to the parameters of a`MIMEType`. + * @since v19.1.0, v18.13.0 + */ + export class MIMEParams { + /** + * Remove all name-value pairs whose name is `name`. + */ + delete(name: string): void; + /** + * Returns an iterator over each of the name-value pairs in the parameters. + * Each item of the iterator is a JavaScript `Array`. The first item of the array + * is the `name`, the second item of the array is the `value`. + */ + entries(): IterableIterator<[name: string, value: string]>; + /** + * Returns the value of the first name-value pair whose name is `name`. If there + * are no such pairs, `null` is returned. + * @return or `null` if there is no name-value pair with the given `name`. + */ + get(name: string): string | null; + /** + * Returns `true` if there is at least one name-value pair whose name is `name`. + */ + has(name: string): boolean; + /** + * Returns an iterator over the names of each name-value pair. + * + * ```js + * import { MIMEType } from 'node:util'; + * + * const { params } = new MIMEType('text/plain;foo=0;bar=1'); + * for (const name of params.keys()) { + * console.log(name); + * } + * // Prints: + * // foo + * // bar + * ``` + */ + keys(): IterableIterator; + /** + * Sets the value in the `MIMEParams` object associated with `name` to`value`. If there are any pre-existing name-value pairs whose names are `name`, + * set the first such pair's value to `value`. + * + * ```js + * import { MIMEType } from 'node:util'; + * + * const { params } = new MIMEType('text/plain;foo=0;bar=1'); + * params.set('foo', 'def'); + * params.set('baz', 'xyz'); + * console.log(params.toString()); + * // Prints: foo=def;bar=1;baz=xyz + * ``` + */ + set(name: string, value: string): void; + /** + * Returns an iterator over the values of each name-value pair. + */ + values(): IterableIterator; + /** + * Returns an iterator over each of the name-value pairs in the parameters. + */ + [Symbol.iterator]: typeof MIMEParams.prototype.entries; + } +} +declare module "util/types" { + import { KeyObject, webcrypto } from "node:crypto"; + /** + * Returns `true` if the value is a built-in [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) or + * [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) instance. + * + * See also `util.types.isArrayBuffer()` and `util.types.isSharedArrayBuffer()`. + * + * ```js + * util.types.isAnyArrayBuffer(new ArrayBuffer()); // Returns true + * util.types.isAnyArrayBuffer(new SharedArrayBuffer()); // Returns true + * ``` + * @since v10.0.0 + */ + function isAnyArrayBuffer(object: unknown): object is ArrayBufferLike; + /** + * Returns `true` if the value is an `arguments` object. + * + * ```js + * function foo() { + * util.types.isArgumentsObject(arguments); // Returns true + * } + * ``` + * @since v10.0.0 + */ + function isArgumentsObject(object: unknown): object is IArguments; + /** + * Returns `true` if the value is a built-in [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) instance. + * This does _not_ include [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) instances. Usually, it is + * desirable to test for both; See `util.types.isAnyArrayBuffer()` for that. + * + * ```js + * util.types.isArrayBuffer(new ArrayBuffer()); // Returns true + * util.types.isArrayBuffer(new SharedArrayBuffer()); // Returns false + * ``` + * @since v10.0.0 + */ + function isArrayBuffer(object: unknown): object is ArrayBuffer; + /** + * Returns `true` if the value is an instance of one of the [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) views, such as typed + * array objects or [`DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView). Equivalent to + * [`ArrayBuffer.isView()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/isView). + * + * ```js + * util.types.isArrayBufferView(new Int8Array()); // true + * util.types.isArrayBufferView(Buffer.from('hello world')); // true + * util.types.isArrayBufferView(new DataView(new ArrayBuffer(16))); // true + * util.types.isArrayBufferView(new ArrayBuffer()); // false + * ``` + * @since v10.0.0 + */ + function isArrayBufferView(object: unknown): object is NodeJS.ArrayBufferView; + /** + * Returns `true` if the value is an [async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). + * This only reports back what the JavaScript engine is seeing; + * in particular, the return value may not match the original source code if + * a transpilation tool was used. + * + * ```js + * util.types.isAsyncFunction(function foo() {}); // Returns false + * util.types.isAsyncFunction(async function foo() {}); // Returns true + * ``` + * @since v10.0.0 + */ + function isAsyncFunction(object: unknown): boolean; + /** + * Returns `true` if the value is a `BigInt64Array` instance. + * + * ```js + * util.types.isBigInt64Array(new BigInt64Array()); // Returns true + * util.types.isBigInt64Array(new BigUint64Array()); // Returns false + * ``` + * @since v10.0.0 + */ + function isBigInt64Array(value: unknown): value is BigInt64Array; + /** + * Returns `true` if the value is a `BigUint64Array` instance. + * + * ```js + * util.types.isBigUint64Array(new BigInt64Array()); // Returns false + * util.types.isBigUint64Array(new BigUint64Array()); // Returns true + * ``` + * @since v10.0.0 + */ + function isBigUint64Array(value: unknown): value is BigUint64Array; + /** + * Returns `true` if the value is a boolean object, e.g. created + * by `new Boolean()`. + * + * ```js + * util.types.isBooleanObject(false); // Returns false + * util.types.isBooleanObject(true); // Returns false + * util.types.isBooleanObject(new Boolean(false)); // Returns true + * util.types.isBooleanObject(new Boolean(true)); // Returns true + * util.types.isBooleanObject(Boolean(false)); // Returns false + * util.types.isBooleanObject(Boolean(true)); // Returns false + * ``` + * @since v10.0.0 + */ + function isBooleanObject(object: unknown): object is Boolean; + /** + * Returns `true` if the value is any boxed primitive object, e.g. created + * by `new Boolean()`, `new String()` or `Object(Symbol())`. + * + * For example: + * + * ```js + * util.types.isBoxedPrimitive(false); // Returns false + * util.types.isBoxedPrimitive(new Boolean(false)); // Returns true + * util.types.isBoxedPrimitive(Symbol('foo')); // Returns false + * util.types.isBoxedPrimitive(Object(Symbol('foo'))); // Returns true + * util.types.isBoxedPrimitive(Object(BigInt(5))); // Returns true + * ``` + * @since v10.11.0 + */ + function isBoxedPrimitive(object: unknown): object is String | Number | BigInt | Boolean | Symbol; + /** + * Returns `true` if the value is a built-in [`DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) instance. + * + * ```js + * const ab = new ArrayBuffer(20); + * util.types.isDataView(new DataView(ab)); // Returns true + * util.types.isDataView(new Float64Array()); // Returns false + * ``` + * + * See also [`ArrayBuffer.isView()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/isView). + * @since v10.0.0 + */ + function isDataView(object: unknown): object is DataView; + /** + * Returns `true` if the value is a built-in [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) instance. + * + * ```js + * util.types.isDate(new Date()); // Returns true + * ``` + * @since v10.0.0 + */ + function isDate(object: unknown): object is Date; + /** + * Returns `true` if the value is a native `External` value. + * + * A native `External` value is a special type of object that contains a + * raw C++ pointer (`void*`) for access from native code, and has no other + * properties. Such objects are created either by Node.js internals or native + * addons. In JavaScript, they are [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) objects with a`null` prototype. + * + * ```c + * #include + * #include + * napi_value result; + * static napi_value MyNapi(napi_env env, napi_callback_info info) { + * int* raw = (int*) malloc(1024); + * napi_status status = napi_create_external(env, (void*) raw, NULL, NULL, &result); + * if (status != napi_ok) { + * napi_throw_error(env, NULL, "napi_create_external failed"); + * return NULL; + * } + * return result; + * } + * ... + * DECLARE_NAPI_PROPERTY("myNapi", MyNapi) + * ... + * ``` + * + * ```js + * const native = require('napi_addon.node'); + * const data = native.myNapi(); + * util.types.isExternal(data); // returns true + * util.types.isExternal(0); // returns false + * util.types.isExternal(new String('foo')); // returns false + * ``` + * + * For further information on `napi_create_external`, refer to `napi_create_external()`. + * @since v10.0.0 + */ + function isExternal(object: unknown): boolean; + /** + * Returns `true` if the value is a built-in [`Float32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) instance. + * + * ```js + * util.types.isFloat32Array(new ArrayBuffer()); // Returns false + * util.types.isFloat32Array(new Float32Array()); // Returns true + * util.types.isFloat32Array(new Float64Array()); // Returns false + * ``` + * @since v10.0.0 + */ + function isFloat32Array(object: unknown): object is Float32Array; + /** + * Returns `true` if the value is a built-in [`Float64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array) instance. + * + * ```js + * util.types.isFloat64Array(new ArrayBuffer()); // Returns false + * util.types.isFloat64Array(new Uint8Array()); // Returns false + * util.types.isFloat64Array(new Float64Array()); // Returns true + * ``` + * @since v10.0.0 + */ + function isFloat64Array(object: unknown): object is Float64Array; + /** + * Returns `true` if the value is a generator function. + * This only reports back what the JavaScript engine is seeing; + * in particular, the return value may not match the original source code if + * a transpilation tool was used. + * + * ```js + * util.types.isGeneratorFunction(function foo() {}); // Returns false + * util.types.isGeneratorFunction(function* foo() {}); // Returns true + * ``` + * @since v10.0.0 + */ + function isGeneratorFunction(object: unknown): object is GeneratorFunction; + /** + * Returns `true` if the value is a generator object as returned from a + * built-in generator function. + * This only reports back what the JavaScript engine is seeing; + * in particular, the return value may not match the original source code if + * a transpilation tool was used. + * + * ```js + * function* foo() {} + * const generator = foo(); + * util.types.isGeneratorObject(generator); // Returns true + * ``` + * @since v10.0.0 + */ + function isGeneratorObject(object: unknown): object is Generator; + /** + * Returns `true` if the value is a built-in [`Int8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array) instance. + * + * ```js + * util.types.isInt8Array(new ArrayBuffer()); // Returns false + * util.types.isInt8Array(new Int8Array()); // Returns true + * util.types.isInt8Array(new Float64Array()); // Returns false + * ``` + * @since v10.0.0 + */ + function isInt8Array(object: unknown): object is Int8Array; + /** + * Returns `true` if the value is a built-in [`Int16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array) instance. + * + * ```js + * util.types.isInt16Array(new ArrayBuffer()); // Returns false + * util.types.isInt16Array(new Int16Array()); // Returns true + * util.types.isInt16Array(new Float64Array()); // Returns false + * ``` + * @since v10.0.0 + */ + function isInt16Array(object: unknown): object is Int16Array; + /** + * Returns `true` if the value is a built-in [`Int32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array) instance. + * + * ```js + * util.types.isInt32Array(new ArrayBuffer()); // Returns false + * util.types.isInt32Array(new Int32Array()); // Returns true + * util.types.isInt32Array(new Float64Array()); // Returns false + * ``` + * @since v10.0.0 + */ + function isInt32Array(object: unknown): object is Int32Array; + /** + * Returns `true` if the value is a built-in [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) instance. + * + * ```js + * util.types.isMap(new Map()); // Returns true + * ``` + * @since v10.0.0 + */ + function isMap( + object: T | {}, + ): object is T extends ReadonlyMap ? (unknown extends T ? never : ReadonlyMap) + : Map; + /** + * Returns `true` if the value is an iterator returned for a built-in [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) instance. + * + * ```js + * const map = new Map(); + * util.types.isMapIterator(map.keys()); // Returns true + * util.types.isMapIterator(map.values()); // Returns true + * util.types.isMapIterator(map.entries()); // Returns true + * util.types.isMapIterator(map[Symbol.iterator]()); // Returns true + * ``` + * @since v10.0.0 + */ + function isMapIterator(object: unknown): boolean; + /** + * Returns `true` if the value is an instance of a [Module Namespace Object](https://tc39.github.io/ecma262/#sec-module-namespace-exotic-objects). + * + * ```js + * import * as ns from './a.js'; + * + * util.types.isModuleNamespaceObject(ns); // Returns true + * ``` + * @since v10.0.0 + */ + function isModuleNamespaceObject(value: unknown): boolean; + /** + * Returns `true` if the value was returned by the constructor of a [built-in `Error` type](https://tc39.es/ecma262/#sec-error-objects). + * + * ```js + * console.log(util.types.isNativeError(new Error())); // true + * console.log(util.types.isNativeError(new TypeError())); // true + * console.log(util.types.isNativeError(new RangeError())); // true + * ``` + * + * Subclasses of the native error types are also native errors: + * + * ```js + * class MyError extends Error {} + * console.log(util.types.isNativeError(new MyError())); // true + * ``` + * + * A value being `instanceof` a native error class is not equivalent to `isNativeError()`returning `true` for that value. `isNativeError()` returns `true` for errors + * which come from a different [realm](https://tc39.es/ecma262/#realm) while `instanceof Error` returns `false`for these errors: + * + * ```js + * const vm = require('node:vm'); + * const context = vm.createContext({}); + * const myError = vm.runInContext('new Error()', context); + * console.log(util.types.isNativeError(myError)); // true + * console.log(myError instanceof Error); // false + * ``` + * + * Conversely, `isNativeError()` returns `false` for all objects which were not + * returned by the constructor of a native error. That includes values + * which are `instanceof` native errors: + * + * ```js + * const myError = { __proto__: Error.prototype }; + * console.log(util.types.isNativeError(myError)); // false + * console.log(myError instanceof Error); // true + * ``` + * @since v10.0.0 + */ + function isNativeError(object: unknown): object is Error; + /** + * Returns `true` if the value is a number object, e.g. created + * by `new Number()`. + * + * ```js + * util.types.isNumberObject(0); // Returns false + * util.types.isNumberObject(new Number(0)); // Returns true + * ``` + * @since v10.0.0 + */ + function isNumberObject(object: unknown): object is Number; + /** + * Returns `true` if the value is a built-in [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). + * + * ```js + * util.types.isPromise(Promise.resolve(42)); // Returns true + * ``` + * @since v10.0.0 + */ + function isPromise(object: unknown): object is Promise; + /** + * Returns `true` if the value is a [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) instance. + * + * ```js + * const target = {}; + * const proxy = new Proxy(target, {}); + * util.types.isProxy(target); // Returns false + * util.types.isProxy(proxy); // Returns true + * ``` + * @since v10.0.0 + */ + function isProxy(object: unknown): boolean; + /** + * Returns `true` if the value is a regular expression object. + * + * ```js + * util.types.isRegExp(/abc/); // Returns true + * util.types.isRegExp(new RegExp('abc')); // Returns true + * ``` + * @since v10.0.0 + */ + function isRegExp(object: unknown): object is RegExp; + /** + * Returns `true` if the value is a built-in [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) instance. + * + * ```js + * util.types.isSet(new Set()); // Returns true + * ``` + * @since v10.0.0 + */ + function isSet( + object: T | {}, + ): object is T extends ReadonlySet ? (unknown extends T ? never : ReadonlySet) : Set; + /** + * Returns `true` if the value is an iterator returned for a built-in [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) instance. + * + * ```js + * const set = new Set(); + * util.types.isSetIterator(set.keys()); // Returns true + * util.types.isSetIterator(set.values()); // Returns true + * util.types.isSetIterator(set.entries()); // Returns true + * util.types.isSetIterator(set[Symbol.iterator]()); // Returns true + * ``` + * @since v10.0.0 + */ + function isSetIterator(object: unknown): boolean; + /** + * Returns `true` if the value is a built-in [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) instance. + * This does _not_ include [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) instances. Usually, it is + * desirable to test for both; See `util.types.isAnyArrayBuffer()` for that. + * + * ```js + * util.types.isSharedArrayBuffer(new ArrayBuffer()); // Returns false + * util.types.isSharedArrayBuffer(new SharedArrayBuffer()); // Returns true + * ``` + * @since v10.0.0 + */ + function isSharedArrayBuffer(object: unknown): object is SharedArrayBuffer; + /** + * Returns `true` if the value is a string object, e.g. created + * by `new String()`. + * + * ```js + * util.types.isStringObject('foo'); // Returns false + * util.types.isStringObject(new String('foo')); // Returns true + * ``` + * @since v10.0.0 + */ + function isStringObject(object: unknown): object is String; + /** + * Returns `true` if the value is a symbol object, created + * by calling `Object()` on a `Symbol` primitive. + * + * ```js + * const symbol = Symbol('foo'); + * util.types.isSymbolObject(symbol); // Returns false + * util.types.isSymbolObject(Object(symbol)); // Returns true + * ``` + * @since v10.0.0 + */ + function isSymbolObject(object: unknown): object is Symbol; + /** + * Returns `true` if the value is a built-in [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) instance. + * + * ```js + * util.types.isTypedArray(new ArrayBuffer()); // Returns false + * util.types.isTypedArray(new Uint8Array()); // Returns true + * util.types.isTypedArray(new Float64Array()); // Returns true + * ``` + * + * See also [`ArrayBuffer.isView()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/isView). + * @since v10.0.0 + */ + function isTypedArray(object: unknown): object is NodeJS.TypedArray; + /** + * Returns `true` if the value is a built-in [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) instance. + * + * ```js + * util.types.isUint8Array(new ArrayBuffer()); // Returns false + * util.types.isUint8Array(new Uint8Array()); // Returns true + * util.types.isUint8Array(new Float64Array()); // Returns false + * ``` + * @since v10.0.0 + */ + function isUint8Array(object: unknown): object is Uint8Array; + /** + * Returns `true` if the value is a built-in [`Uint8ClampedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray) instance. + * + * ```js + * util.types.isUint8ClampedArray(new ArrayBuffer()); // Returns false + * util.types.isUint8ClampedArray(new Uint8ClampedArray()); // Returns true + * util.types.isUint8ClampedArray(new Float64Array()); // Returns false + * ``` + * @since v10.0.0 + */ + function isUint8ClampedArray(object: unknown): object is Uint8ClampedArray; + /** + * Returns `true` if the value is a built-in [`Uint16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array) instance. + * + * ```js + * util.types.isUint16Array(new ArrayBuffer()); // Returns false + * util.types.isUint16Array(new Uint16Array()); // Returns true + * util.types.isUint16Array(new Float64Array()); // Returns false + * ``` + * @since v10.0.0 + */ + function isUint16Array(object: unknown): object is Uint16Array; + /** + * Returns `true` if the value is a built-in [`Uint32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array) instance. + * + * ```js + * util.types.isUint32Array(new ArrayBuffer()); // Returns false + * util.types.isUint32Array(new Uint32Array()); // Returns true + * util.types.isUint32Array(new Float64Array()); // Returns false + * ``` + * @since v10.0.0 + */ + function isUint32Array(object: unknown): object is Uint32Array; + /** + * Returns `true` if the value is a built-in [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) instance. + * + * ```js + * util.types.isWeakMap(new WeakMap()); // Returns true + * ``` + * @since v10.0.0 + */ + function isWeakMap(object: unknown): object is WeakMap; + /** + * Returns `true` if the value is a built-in [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) instance. + * + * ```js + * util.types.isWeakSet(new WeakSet()); // Returns true + * ``` + * @since v10.0.0 + */ + function isWeakSet(object: unknown): object is WeakSet; + /** + * Returns `true` if `value` is a `KeyObject`, `false` otherwise. + * @since v16.2.0 + */ + function isKeyObject(object: unknown): object is KeyObject; + /** + * Returns `true` if `value` is a `CryptoKey`, `false` otherwise. + * @since v16.2.0 + */ + function isCryptoKey(object: unknown): object is webcrypto.CryptoKey; +} +declare module "node:util" { + export * from "util"; +} +declare module "node:util/types" { + export * from "util/types"; +} diff --git a/src/views/ClaimView.vue b/src/views/ClaimView.vue new file mode 100644 index 0000000..5dcfa6a --- /dev/null +++ b/src/views/ClaimView.vue @@ -0,0 +1,411 @@ + + + diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index 84c6c34..cd9db39 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -67,8 +67,8 @@ showGiveTotals ? "Total" : showGiveConfirmed - ? "Confirmed" - : "Unconfirmed" + ? "Confirmed" + : "Unconfirmed" }}
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 8db8c24..a5189a4 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -108,8 +108,10 @@
- {{ this.giveDescription(record) }} + + +
@@ -341,6 +343,13 @@ export default class HomeView extends Vue { return giverInfo + " gave" + gaveRecipientInfo + ": " + gaveAmount; } + onClickLoadClaim(jwtId: string) { + const route = { + path: "/claim/" + encodeURIComponent(jwtId), + }; + this.$router.push(route); + } + displayAmount(code: string, amt: number) { return "" + amt + " " + this.currencyShortWordForCode(code, amt === 1); } diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index a72ff9d..07cdef1 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -1,5 +1,5 @@