Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@frehner/apphistory

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@frehner/apphistory - npm Package Compare versions

Comparing version 0.0.5 to 0.0.6

38

build/esm/appHistory.js

@@ -88,7 +88,21 @@ function _defineProperty(obj, key, value) {

async update(param1, param2) {
async navigate(param1, param2) {
const options = this.getOptionsFromParams(param1, param2);
if (options?.replace) {
return this.updateNavigation(options);
} else {
return this.pushNavigation(options);
}
}
async updateNavigation(options) {
// used in currentchange event
const startTime = performance.now();
const options = this.getOptionsFromParams(param1, param2); // location.href updates here
if (options?.replace && Object.keys(options).length === 1) {
throw new Error("Must include more options than just {'replace: true'}");
} // location.href updates here
this.current.__updateEntry(options ?? {});

@@ -115,7 +129,6 @@

async push(param1, param2) {
const previousEntry = this.current; // used in the currentchange event
async pushNavigation(options) {
// used in the currentchange event
const startTime = performance.now();
const options = this.getOptionsFromParams(param1, param2);
const previousEntry = this.current;
const upcomingEntry = new AppHistoryEntry(options, this.current);

@@ -130,3 +143,3 @@ const respondWithPromiseArray = this.sendNavigateEvent(upcomingEntry, options?.navigateInfo);

if (upcomingURL.origin === window.location.origin) {
window.history.pushState(null, "", upcomingEntry.url);
window.history.pushState(options?.state, "", upcomingEntry.url);
} else {

@@ -240,3 +253,3 @@ window.location.assign(upcomingEntry.url);

async navigateTo(key, navigationOptions) {
async goTo(key, navigationOptions) {
const entryIndex = this.entries.findIndex(entry => entry.key === key);

@@ -562,3 +575,3 @@

if (evt.target && evt.target instanceof HTMLElement) {
// on anchor/area clicks, fire 'appHistory.push()'
// on anchor/area clicks, fire 'appHistory.navigate()'
const linkTag = evt.target.nodeName === "A" || evt.target.nodeName === "AREA" ? evt.target : evt.target.closest("a") ?? evt.target.closest("area");

@@ -568,3 +581,8 @@

evt.preventDefault();
window.appHistory.push(linkTag.href).catch(err => {
window.appHistory.navigate({
url: linkTag.href,
navigateInfo: {
type: `${linkTag.nodeName.toLowerCase()}-click`
}
}).catch(err => {
setTimeout(() => {

@@ -571,0 +589,0 @@ throw err;

@@ -1,2 +0,2 @@

function t(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}class e{constructor(){t(this,"current",void 0),t(this,"entries",void 0),t(this,"canGoBack",void 0),t(this,"canGoForward",void 0),t(this,"eventListeners",{navigate:[],currentchange:[],navigatesuccess:[],navigateerror:[]}),t(this,"onEventListeners",{navigate:null,currentchange:null,navigatesuccess:null,navigateerror:null}),this.current=new n({url:window.location.href}),this.current.finished=!0,this.current.__updateEntry(void 0,0),this.entries=[this.current],this.canGoBack=!1,this.canGoForward=!1}getOptionsFromParams(t,e){let n;switch(typeof t){case"string":e&&"object"==typeof e?(n=e,n.url=t):n={url:t};break;case"object":t&&(n=t)}return n}async update(t,e){const n=performance.now(),i=this.getOptionsFromParams(t,e);this.current.__updateEntry(i??{}),this.current.finished=!1;const s=this.sendNavigateEvent(this.current,i?.navigateInfo);return this.sendCurrentChangeEvent(n),Promise.all(s).then((()=>{this.current.finished=!0,this.current.__fireEventListenersForEvent("finish"),this.sendNavigateSuccessEvent()})).catch((t=>{throw this.current.finished=!0,this.current.__fireEventListenersForEvent("finish"),this.sendNavigateErrorEvent(t),t}))}async push(t,e){const i=this.current,s=performance.now(),r=this.getOptionsFromParams(t,e),a=new n(r,this.current),o=this.sendNavigateEvent(a,r?.navigateInfo);this.current.__fireEventListenersForEvent("navigatefrom");const h=this.entries.findIndex((t=>t.key===i.key));let c;return new URL(a.url,window.location.origin+window.location.pathname).origin===window.location.origin?window.history.pushState(null,"",a.url):window.location.assign(a.url),this.current=a,this.canGoBack=!0,this.canGoForward=!1,this.sendCurrentChangeEvent(s),this.current.__fireEventListenersForEvent("navigateto"),i.finished||i.__fireAbortForAssociatedEvent(),a.__getAssociatedAbortSignal()?.addEventListener("abort",(()=>{c=new DOMException(`A new entry was added before the promises passed to respondWith() resolved for entry with url ${a.url}`,"AbortError"),this.sendNavigateErrorEvent(c)})),this.entries.slice(h+1).forEach((t=>{t.__updateEntry(void 0,-1),t.__fireEventListenersForEvent("dispose")})),this.entries=[...this.entries.slice(0,h+1),this.current].map(((t,e)=>(t.__updateEntry(void 0,e),t))),Promise.all(o).then((()=>{if(c)throw c;a.finished=!0,a.__fireEventListenersForEvent("finish"),this.sendNavigateSuccessEvent()})).catch((t=>{if(t&&t===c)throw t;throw a.finished=!0,a.__fireEventListenersForEvent("finish"),this.sendNavigateErrorEvent(t),t}))}set onnavigate(t){this.addOnEventListener("navigate",t)}set oncurrentchange(t){this.addOnEventListener("currentchange",t)}set onnavigatesuccess(t){this.addOnEventListener("navigatesuccess",t)}set onnavigateerror(t){this.addOnEventListener("navigateerror",t)}addOnEventListener(t,e){this.onEventListeners[t]&&("navigate"===t?this.eventListeners.navigate=this.eventListeners.navigate.filter((t=>t!==this.onEventListeners.navigate)):this.eventListeners[t]=this.eventListeners[t].filter((e=>e!==this.onEventListeners[t]))),this.onEventListeners[t]=e,e&&this.addEventListener(t,e)}addEventListener(t,e){if("navigate"!==t&&"currentchange"!==t&&"navigatesuccess"!==t&&"navigateerror"!==t)throw new Error("appHistory does not listen for that event at this time");!function(t,e){return"navigate"===t}(t)?this.eventListeners[t].includes(e)||this.eventListeners[t].push(e):this.eventListeners.navigate.includes(e)||this.eventListeners.navigate.push(e)}async navigateTo(t,e){const n=this.entries.findIndex((e=>e.key===t));if(-1===n)throw new DOMException("InvalidStateError");const i=this.entries[n];await this.changeCurrentEntry(i,e)}async back(t){const e=this.entries.findIndex((t=>t.key===this.current.key));if(0===e)throw new DOMException("InvalidStateError");const n=this.entries[e-1];await this.changeCurrentEntry(n,t)}async forward(t){const e=this.entries.findIndex((t=>t.key===this.current.key));if(e===this.entries.length-1)throw new DOMException("InvalidStateError");const n=this.entries[e+1];await this.changeCurrentEntry(n,t)}async changeCurrentEntry(t,e){await this.sendNavigateEvent(t,e?.navigateInfo),this.current.__fireEventListenersForEvent("navigatefrom"),this.current=t,this.current.__fireEventListenersForEvent("navigateto"),this.canGoBack=this.current.index>0,this.canGoForward=this.current.index<this.entries.length-1}sendNavigateEvent(t,e){const n=[],s=new URL(t.url,window.location.origin+window.location.pathname),r=s.origin===window.location.origin,a=new i({cancelable:!0,userInitiated:!0,hashChange:t.sameDocument&&s.hash!==window.location.hash,destination:t,info:e,canRespond:r,respondWith:e=>{if(!r)throw new DOMException("Cannot call AppHistoryNavigateEvent.respondWith() if AppHistoryNavigateEvent.canRespond is false","SecurityError");t.sameDocument=!0,n.push(e)}});if(t.__associateNavigateEvent(a),this.eventListeners.navigate.forEach((t=>{try{t.call(this,a)}catch(t){setTimeout((()=>{throw t}))}})),a.defaultPrevented)throw new DOMException("AbortError");return n}sendCurrentChangeEvent(t){this.eventListeners.currentchange.forEach((e=>{try{e.call(this,new s({startTime:t}))}catch(t){setTimeout((()=>{throw t}))}}))}sendNavigateSuccessEvent(){this.eventListeners.navigatesuccess.forEach((t=>{try{t(new CustomEvent("TODO figure out the correct event"))}catch(t){setTimeout((()=>{throw t}))}}))}sendNavigateErrorEvent(t){this.eventListeners.navigateerror.forEach((e=>{try{e(new CustomEvent("TODO figure out the correct event",{detail:{error:t}}))}catch(t){setTimeout((()=>{throw t}))}}))}}class n{constructor(e,n){t(this,"key",void 0),t(this,"url",void 0),t(this,"sameDocument",void 0),t(this,"index",void 0),t(this,"_state",void 0),t(this,"finished",void 0),t(this,"latestNavigateEvent",void 0),t(this,"eventListeners",{navigateto:[],navigatefrom:[],dispose:[],finish:[]}),this._state=null,e?.state&&(this._state=e.state),this.key=Math.random().toString(36).substr(2,10),this.index=-1,this.finished=!1;const i=e?.url??n?.url??window.location.pathname;this.url=i;const s=new URL(i,window.location.origin+window.location.pathname);this.sameDocument=s.origin===window.location.origin&&s.pathname===window.location.pathname}getState(){return JSON.parse(JSON.stringify(this._state))}addEventListener(t,e){this.eventListeners[t].includes(e)||this.eventListeners[t].push(e)}__updateEntry(t,e){void 0!==t?.state&&(this._state=t.state),t?.url&&(this.url=t.url),"number"==typeof e&&(this.index=e)}__fireEventListenersForEvent(t){const e=new r({detail:{target:this}},t);this.eventListeners[t].map((t=>{try{t(e)}catch(t){setTimeout((()=>{throw t}))}}))}__associateNavigateEvent(t){this.latestNavigateEvent=t}__fireAbortForAssociatedEvent(){this.latestNavigateEvent?.__abort()}__getAssociatedAbortSignal(){return this.latestNavigateEvent?.signal}}class i extends Event{constructor(e){super("AppHistoryNavigateEvent",e),t(this,"userInitiated",void 0),t(this,"hashChange",void 0),t(this,"destination",void 0),t(this,"formData",void 0),t(this,"info",void 0),t(this,"canRespond",void 0),t(this,"respondWith",void 0),t(this,"signal",void 0),t(this,"abortController",void 0),this.userInitiated=e.userInitiated??!1,this.hashChange=e.hashChange??!1,this.destination=e.destination,this.formData=e.formData,this.canRespond=e.canRespond,this.respondWith=e.respondWith,this.info=e.info,this.abortController=new AbortController,this.signal=this.abortController.signal}__abort(){this.abortController.abort()}}class s extends Event{constructor(e){super("AppHistoryCurrentChangeEvent",e),t(this,"startTime",void 0),this.startTime=e.startTime}}class r extends CustomEvent{constructor(t,e){super(e,t)}}function a(t){"appHistory"in window||(Object.defineProperty(window,"appHistory",{value:new e,enumerable:!0,configurable:t?.configurable??!1}),window.addEventListener("click",o))}function o(t){if(t.target&&t.target instanceof HTMLElement){const e="A"===t.target.nodeName||"AREA"===t.target.nodeName?t.target:t.target.closest("a")??t.target.closest("area");e&&(t.preventDefault(),window.appHistory.push(e.href).catch((t=>{setTimeout((()=>{throw t}))})))}}export{e as AppHistory,a as useBrowserPolyfill};
function t(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}class e{constructor(){t(this,"current",void 0),t(this,"entries",void 0),t(this,"canGoBack",void 0),t(this,"canGoForward",void 0),t(this,"eventListeners",{navigate:[],currentchange:[],navigatesuccess:[],navigateerror:[]}),t(this,"onEventListeners",{navigate:null,currentchange:null,navigatesuccess:null,navigateerror:null}),this.current=new n({url:window.location.href}),this.current.finished=!0,this.current.__updateEntry(void 0,0),this.entries=[this.current],this.canGoBack=!1,this.canGoForward=!1}getOptionsFromParams(t,e){let n;switch(typeof t){case"string":e&&"object"==typeof e?(n=e,n.url=t):n={url:t};break;case"object":t&&(n=t)}return n}async navigate(t,e){const n=this.getOptionsFromParams(t,e);return n?.replace?this.updateNavigation(n):this.pushNavigation(n)}async updateNavigation(t){const e=performance.now();if(t?.replace&&1===Object.keys(t).length)throw new Error("Must include more options than just {'replace: true'}");this.current.__updateEntry(t??{}),this.current.finished=!1;const n=this.sendNavigateEvent(this.current,t?.navigateInfo);return this.sendCurrentChangeEvent(e),Promise.all(n).then((()=>{this.current.finished=!0,this.current.__fireEventListenersForEvent("finish"),this.sendNavigateSuccessEvent()})).catch((t=>{throw this.current.finished=!0,this.current.__fireEventListenersForEvent("finish"),this.sendNavigateErrorEvent(t),t}))}async pushNavigation(t){const e=performance.now(),i=this.current,s=new n(t,this.current),r=this.sendNavigateEvent(s,t?.navigateInfo);this.current.__fireEventListenersForEvent("navigatefrom");const a=this.entries.findIndex((t=>t.key===i.key));let o;return new URL(s.url,window.location.origin+window.location.pathname).origin===window.location.origin?window.history.pushState(t?.state,"",s.url):window.location.assign(s.url),this.current=s,this.canGoBack=!0,this.canGoForward=!1,this.sendCurrentChangeEvent(e),this.current.__fireEventListenersForEvent("navigateto"),i.finished||i.__fireAbortForAssociatedEvent(),s.__getAssociatedAbortSignal()?.addEventListener("abort",(()=>{o=new DOMException(`A new entry was added before the promises passed to respondWith() resolved for entry with url ${s.url}`,"AbortError"),this.sendNavigateErrorEvent(o)})),this.entries.slice(a+1).forEach((t=>{t.__updateEntry(void 0,-1),t.__fireEventListenersForEvent("dispose")})),this.entries=[...this.entries.slice(0,a+1),this.current].map(((t,e)=>(t.__updateEntry(void 0,e),t))),Promise.all(r).then((()=>{if(o)throw o;s.finished=!0,s.__fireEventListenersForEvent("finish"),this.sendNavigateSuccessEvent()})).catch((t=>{if(t&&t===o)throw t;throw s.finished=!0,s.__fireEventListenersForEvent("finish"),this.sendNavigateErrorEvent(t),t}))}set onnavigate(t){this.addOnEventListener("navigate",t)}set oncurrentchange(t){this.addOnEventListener("currentchange",t)}set onnavigatesuccess(t){this.addOnEventListener("navigatesuccess",t)}set onnavigateerror(t){this.addOnEventListener("navigateerror",t)}addOnEventListener(t,e){this.onEventListeners[t]&&("navigate"===t?this.eventListeners.navigate=this.eventListeners.navigate.filter((t=>t!==this.onEventListeners.navigate)):this.eventListeners[t]=this.eventListeners[t].filter((e=>e!==this.onEventListeners[t]))),this.onEventListeners[t]=e,e&&this.addEventListener(t,e)}addEventListener(t,e){if("navigate"!==t&&"currentchange"!==t&&"navigatesuccess"!==t&&"navigateerror"!==t)throw new Error("appHistory does not listen for that event at this time");!function(t,e){return"navigate"===t}(t)?this.eventListeners[t].includes(e)||this.eventListeners[t].push(e):this.eventListeners.navigate.includes(e)||this.eventListeners.navigate.push(e)}async goTo(t,e){const n=this.entries.findIndex((e=>e.key===t));if(-1===n)throw new DOMException("InvalidStateError");const i=this.entries[n];await this.changeCurrentEntry(i,e)}async back(t){const e=this.entries.findIndex((t=>t.key===this.current.key));if(0===e)throw new DOMException("InvalidStateError");const n=this.entries[e-1];await this.changeCurrentEntry(n,t)}async forward(t){const e=this.entries.findIndex((t=>t.key===this.current.key));if(e===this.entries.length-1)throw new DOMException("InvalidStateError");const n=this.entries[e+1];await this.changeCurrentEntry(n,t)}async changeCurrentEntry(t,e){await this.sendNavigateEvent(t,e?.navigateInfo),this.current.__fireEventListenersForEvent("navigatefrom"),this.current=t,this.current.__fireEventListenersForEvent("navigateto"),this.canGoBack=this.current.index>0,this.canGoForward=this.current.index<this.entries.length-1}sendNavigateEvent(t,e){const n=[],s=new URL(t.url,window.location.origin+window.location.pathname),r=s.origin===window.location.origin,a=new i({cancelable:!0,userInitiated:!0,hashChange:t.sameDocument&&s.hash!==window.location.hash,destination:t,info:e,canRespond:r,respondWith:e=>{if(!r)throw new DOMException("Cannot call AppHistoryNavigateEvent.respondWith() if AppHistoryNavigateEvent.canRespond is false","SecurityError");t.sameDocument=!0,n.push(e)}});if(t.__associateNavigateEvent(a),this.eventListeners.navigate.forEach((t=>{try{t.call(this,a)}catch(t){setTimeout((()=>{throw t}))}})),a.defaultPrevented)throw new DOMException("AbortError");return n}sendCurrentChangeEvent(t){this.eventListeners.currentchange.forEach((e=>{try{e.call(this,new s({startTime:t}))}catch(t){setTimeout((()=>{throw t}))}}))}sendNavigateSuccessEvent(){this.eventListeners.navigatesuccess.forEach((t=>{try{t(new CustomEvent("TODO figure out the correct event"))}catch(t){setTimeout((()=>{throw t}))}}))}sendNavigateErrorEvent(t){this.eventListeners.navigateerror.forEach((e=>{try{e(new CustomEvent("TODO figure out the correct event",{detail:{error:t}}))}catch(t){setTimeout((()=>{throw t}))}}))}}class n{constructor(e,n){t(this,"key",void 0),t(this,"url",void 0),t(this,"sameDocument",void 0),t(this,"index",void 0),t(this,"_state",void 0),t(this,"finished",void 0),t(this,"latestNavigateEvent",void 0),t(this,"eventListeners",{navigateto:[],navigatefrom:[],dispose:[],finish:[]}),this._state=null,e?.state&&(this._state=e.state),this.key=Math.random().toString(36).substr(2,10),this.index=-1,this.finished=!1;const i=e?.url??n?.url??window.location.pathname;this.url=i;const s=new URL(i,window.location.origin+window.location.pathname);this.sameDocument=s.origin===window.location.origin&&s.pathname===window.location.pathname}getState(){return JSON.parse(JSON.stringify(this._state))}addEventListener(t,e){this.eventListeners[t].includes(e)||this.eventListeners[t].push(e)}__updateEntry(t,e){void 0!==t?.state&&(this._state=t.state),t?.url&&(this.url=t.url),"number"==typeof e&&(this.index=e)}__fireEventListenersForEvent(t){const e=new r({detail:{target:this}},t);this.eventListeners[t].map((t=>{try{t(e)}catch(t){setTimeout((()=>{throw t}))}}))}__associateNavigateEvent(t){this.latestNavigateEvent=t}__fireAbortForAssociatedEvent(){this.latestNavigateEvent?.__abort()}__getAssociatedAbortSignal(){return this.latestNavigateEvent?.signal}}class i extends Event{constructor(e){super("AppHistoryNavigateEvent",e),t(this,"userInitiated",void 0),t(this,"hashChange",void 0),t(this,"destination",void 0),t(this,"formData",void 0),t(this,"info",void 0),t(this,"canRespond",void 0),t(this,"respondWith",void 0),t(this,"signal",void 0),t(this,"abortController",void 0),this.userInitiated=e.userInitiated??!1,this.hashChange=e.hashChange??!1,this.destination=e.destination,this.formData=e.formData,this.canRespond=e.canRespond,this.respondWith=e.respondWith,this.info=e.info,this.abortController=new AbortController,this.signal=this.abortController.signal}__abort(){this.abortController.abort()}}class s extends Event{constructor(e){super("AppHistoryCurrentChangeEvent",e),t(this,"startTime",void 0),this.startTime=e.startTime}}class r extends CustomEvent{constructor(t,e){super(e,t)}}function a(t){"appHistory"in window||(Object.defineProperty(window,"appHistory",{value:new e,enumerable:!0,configurable:t?.configurable??!1}),window.addEventListener("click",o))}function o(t){if(t.target&&t.target instanceof HTMLElement){const e="A"===t.target.nodeName||"AREA"===t.target.nodeName?t.target:t.target.closest("a")??t.target.closest("area");e&&(t.preventDefault(),window.appHistory.navigate({url:e.href,navigateInfo:{type:`${e.nodeName.toLowerCase()}-click`}}).catch((t=>{setTimeout((()=>{throw t}))})))}}export{e as AppHistory,a as useBrowserPolyfill};
//# sourceMappingURL=appHistory.min.js.map

@@ -9,6 +9,6 @@ export declare class AppHistory {

private getOptionsFromParams;
update(fullOptions?: AppHistoryPushOrUpdateFullOptions): Promise<undefined>;
update(url?: string, options?: AppHistoryPushOrUpdateOptions): Promise<undefined>;
push(fullOptions?: AppHistoryPushOrUpdateFullOptions): Promise<undefined>;
push(url?: string, options?: AppHistoryPushOrUpdateOptions): Promise<undefined>;
navigate(fullOptions?: AppHistoryPushOrUpdateFullOptions): Promise<undefined>;
navigate(url?: string, options?: AppHistoryNavigateOptions): Promise<undefined>;
private updateNavigation;
private pushNavigation;
private onEventListeners;

@@ -21,3 +21,3 @@ set onnavigate(callback: AppHistoryNavigateEventListener);

addEventListener(eventName: keyof AppHistoryEventListeners, callback: AppHistoryNavigateEventListener | EventListener): void;
navigateTo(key: AppHistoryEntryKey, navigationOptions?: AppHistoryNavigationOptions): Promise<undefined>;
goTo(key: AppHistoryEntryKey, navigationOptions?: AppHistoryNavigationOptions): Promise<undefined>;
back(navigationOptions?: AppHistoryNavigationOptions): Promise<undefined>;

@@ -42,3 +42,3 @@ forward(navigationOptions?: AppHistoryNavigationOptions): Promise<undefined>;

/** Provides a JSON.parse(JSON.stringify()) copy of the Entry's state. */
getState(): any | null;
getState(): unknown;
addEventListener(eventName: keyof AppHistoryEntryEventListeners, callback: EventListener): void;

@@ -71,8 +71,9 @@ /** DO NOT USE; use appHistory.update() instead */

interface AppHistoryNavigationOptions {
navigateInfo?: any;
navigateInfo?: unknown;
}
interface AppHistoryPushOrUpdateOptions extends AppHistoryNavigationOptions {
state?: any | null;
interface AppHistoryNavigateOptions extends AppHistoryNavigationOptions {
state?: unknown;
replace?: boolean;
}
interface AppHistoryPushOrUpdateFullOptions extends AppHistoryPushOrUpdateOptions {
interface AppHistoryPushOrUpdateFullOptions extends AppHistoryNavigateOptions {
url?: string;

@@ -85,3 +86,3 @@ }

formData?: null;
info: any;
info: unknown;
canRespond: boolean;

@@ -88,0 +89,0 @@ respondWith: (respondWithPromise: Promise<undefined>) => void;

# AppHistory
## 0.0.6
- `appHistory.push()` is now `appHistory.navigate()`
- `appHistory.update()` is now `appHistory.navigate({replace: true})`
- `appHistory.navigateTo()` is now `appHistory.goTo()`
- add a catch to polyfill's onclick handler so it doesn't crash
- change TS types from any to unknown
- add package exports
## 0.0.5

@@ -4,0 +13,0 @@

{
"name": "@frehner/apphistory",
"version": "0.0.5",
"version": "0.0.6",
"description": "A polyfill for the appHistory proposal. Not ready for production",
"main": "build/esm/appHistory.min.js",
"module": "build/esm/appHistory.min.js",
"exports": {
"development": "./build/esm/appHistory.js",
"default": "./build/esm/appHistory.min.js"
},
"types": "build/types/appHistory.d.ts",

@@ -7,0 +12,0 @@ "scripts": {

@@ -25,9 +25,9 @@ import { AppHistory } from "./appHistory";

await appHistory.push("/newUrl");
await appHistory.navigate("/newUrl");
expect(appHistory.current.sameDocument).toBe(false);
await appHistory.push("/newUrl#test");
await appHistory.navigate("/newUrl#test");
expect(appHistory.current.sameDocument).toBe(true);
await appHistory.push("#new-test");
await appHistory.navigate("#new-test");
expect(appHistory.current.sameDocument).toBe(true);

@@ -41,3 +41,3 @@

};
await appHistory.push("/url2");
await appHistory.navigate("/url2");
expect(navigateEvent.destination.sameDocument).toBe(true);

@@ -51,3 +51,3 @@ expect(appHistory.current.sameDocument).toBe(true);

await appHistory.push("https://example.com");
await appHistory.navigate("https://example.com");
expect(appHistory.current.sameDocument).toBe(false);

@@ -60,6 +60,6 @@ expect(window.location.assign.mock.calls.length).toBe(1);

describe("update", () => {
describe("navigate with replace: true", () => {
it("only url: updates url but not the state", async () => {
const appHistory = new AppHistory();
await appHistory.update({ state: "test" });
await appHistory.navigate({ state: "test", replace: true });

@@ -70,3 +70,3 @@ const { url: oldUrl } = appHistory.current;

const updatedUrl = "/newUrl";
await appHistory.update({ url: updatedUrl });
await appHistory.navigate({ url: updatedUrl, replace: true });

@@ -88,3 +88,3 @@ const { url: newUrl } = appHistory.current;

const updatedState = "newState";
await appHistory.update({ state: updatedState });
await appHistory.navigate({ state: updatedState, replace: true });

@@ -101,3 +101,3 @@ const { url: newUrl } = appHistory.current;

const appHistory = new AppHistory();
await appHistory.update({ state: "before" });
await appHistory.navigate({ state: "before", replace: true });

@@ -107,3 +107,3 @@ const oldState = appHistory.current.getState();

const updatedState = null;
await appHistory.update({ state: updatedState });
await appHistory.navigate({ state: updatedState, replace: true });

@@ -124,3 +124,7 @@ const newState = appHistory.current.getState();

const updatedUrl = "/newUrl";
await appHistory.update({ state: updatedState, url: updatedUrl });
await appHistory.navigate({
state: updatedState,
url: updatedUrl,
replace: true,
});

@@ -143,3 +147,3 @@ const { url: newUrl } = appHistory.current;

const newState = "newState";
await appHistory.update({ state: newState });
await appHistory.navigate({ state: newState, replace: true });

@@ -157,5 +161,5 @@ const newEntries = appHistory.entries;

// add some entries
await appHistory.push({ url: "/test1" });
await appHistory.navigate({ url: "/test1" });
const test1 = appHistory.current;
await appHistory.push({ url: "/test2" });
await appHistory.navigate({ url: "/test2" });

@@ -165,3 +169,3 @@ await appHistory.back();

await appHistory.update({ url: "/newTest1" });
await appHistory.navigate({ url: "/newTest1", replace: true });

@@ -180,5 +184,5 @@ expect(appHistory.entries.map((entry) => entry.url)).toEqual([

// add some entries
await appHistory.push({ url: "/test1" });
await appHistory.navigate({ url: "/test1" });
const key2 = appHistory.current.key;
await appHistory.push({ url: "/test2" });
await appHistory.navigate({ url: "/test2" });
const key3 = appHistory.current.key;

@@ -189,3 +193,3 @@

await appHistory.update({ url: "/newTest1" });
await appHistory.navigate({ url: "/newTest1", replace: true });

@@ -200,5 +204,8 @@ expect(appHistory.entries.map((entry) => entry.key)).toEqual([

it.todo(
"should not allow 'update()' with no params. https://github.com/WICG/app-history/issues/52"
);
it("should not allow navigate({replace: true}) with no other options", async () => {
const appHistory = new AppHistory();
await expect(appHistory.navigate({ replace: true })).rejects.toThrowError(
"Must include more options than just {'replace: true'}"
);
});
});

@@ -209,6 +216,6 @@

const appHistory = new AppHistory();
await appHistory.update({ state: "test" });
await appHistory.navigate({ state: "test", replace: true });
const oldEntry = appHistory.current;
await appHistory.push();
await appHistory.navigate();

@@ -222,3 +229,3 @@ expect(appHistory.current.getState()).toBeNull();

const appHistory = new AppHistory();
await appHistory.push("/newUrl", { state: "newState" });
await appHistory.navigate("/newUrl", { state: "newState" });

@@ -236,3 +243,3 @@ expect(appHistory.current.url).toBe("/newUrl");

const newState = "newState";
await appHistory.push({ state: newState });
await appHistory.navigate({ state: newState });

@@ -247,7 +254,7 @@ expect(appHistory.current.getState()).toEqual(newState);

const appHistory = new AppHistory();
await appHistory.update({ state: "test" });
await appHistory.navigate({ state: "test", replace: true });
const oldEntry = appHistory.current;
const newUrl = "newUrl";
await appHistory.push({ url: newUrl });
await appHistory.navigate({ url: newUrl });

@@ -267,3 +274,3 @@ expect(appHistory.current.url).toEqual(newUrl);

const newState = "test";
await appHistory.push({ url: newUrl, state: newState });
await appHistory.navigate({ url: newUrl, state: newState });

@@ -279,4 +286,4 @@ expect(appHistory.current.url).toEqual(newUrl);

const appHistory = new AppHistory();
await appHistory.push({ url: "/temp1" });
await appHistory.push({ url: "/temp2" });
await appHistory.navigate({ url: "/temp1" });
await appHistory.navigate({ url: "/temp2" });

@@ -294,12 +301,12 @@ // this test will have to change when I actually put in a sane url in the constructor

const goToEntryKey = appHistory.current.key;
await appHistory.push({ url: "/temp1" });
await appHistory.push({ url: "/temp2" });
await appHistory.navigate({ url: "/temp1" });
await appHistory.navigate({ url: "/temp2" });
expect(appHistory.entries.length).toBe(3);
await appHistory.navigateTo(goToEntryKey);
await appHistory.goTo(goToEntryKey);
expect(appHistory.entries.length).toBe(3);
await appHistory.push({ url: "/temp3" });
await appHistory.navigate({ url: "/temp3" });

@@ -319,7 +326,7 @@ expect(appHistory.entries.length).toBe(2);

await appHistory.push();
await appHistory.navigate();
expect(appHistory.current.index).toBe(1);
await appHistory.push();
await appHistory.navigate();

@@ -339,3 +346,3 @@ expect(appHistory.current.index).toBe(2);

await appHistory.push();
await appHistory.navigate();
expect(appHistory.current.index).toBe(1);

@@ -347,3 +354,3 @@ expect(appHistory.entries.map((entry) => entry.index)).toEqual([0, 1]);

const appHistory = new AppHistory();
await appHistory.push();
await appHistory.navigate();

@@ -364,3 +371,3 @@ expect(appHistory.canGoBack).toBe(true);

await appHistory.push();
await appHistory.navigate();
});

@@ -376,3 +383,3 @@

const newUrl = "/newUrl";
await appHistory.push({ url: newUrl });
await appHistory.navigate({ url: newUrl });

@@ -392,3 +399,3 @@ expect(navEventObject instanceof Event).toBe(true);

await expect(appHistory.push({ url: newUrl })).rejects.toThrow(
await expect(appHistory.navigate({ url: newUrl })).rejects.toThrow(
DOMException

@@ -411,3 +418,3 @@ );

await appHistory.update({ url: "/temp", navigateInfo });
await appHistory.navigate({ url: "/temp", navigateInfo, replace: true });

@@ -429,3 +436,3 @@ expect(expectedInfo).toBeTruthy();

await appHistory.push({ navigateInfo });
await appHistory.navigate({ navigateInfo });

@@ -442,3 +449,3 @@ expect(expectedInfo).toBeTruthy();

appHistory.addEventListener("navigate", () => {
listenerEvents.push("1");
listenerEvents.navigate("1");
throw new Error("test");

@@ -448,3 +455,3 @@ });

appHistory.addEventListener("navigate", () => {
listenerEvents.push("2");
listenerEvents.navigate("2");
});

@@ -468,3 +475,3 @@

await appHistory.push();
await appHistory.navigate();

@@ -476,3 +483,3 @@ expect(timesCalled).toBe(1);

const appHistory = new AppHistory();
await appHistory.push("/path");
await appHistory.navigate("/path");

@@ -484,3 +491,3 @@ appHistory.addEventListener("navigate", (evt) => {

await appHistory.push("/otherPath");
await appHistory.navigate("/otherPath");
});

@@ -490,3 +497,3 @@

const appHistory = new AppHistory();
await appHistory.push("/broken");
await appHistory.navigate("/broken");

@@ -498,3 +505,3 @@ appHistory.addEventListener("navigate", (evt) => {

await appHistory.push("/broken#test");
await appHistory.navigate("/broken#test");
});

@@ -504,3 +511,3 @@

const appHistory = new AppHistory();
await appHistory.push("/path#test");
await appHistory.navigate("/path#test");

@@ -512,3 +519,3 @@ appHistory.addEventListener("navigate", (evt) => {

await appHistory.push("/path?search=new#test");
await appHistory.navigate("/path?search=new#test");
});

@@ -532,3 +539,3 @@

MockLocation.mock();
await appHistory.push("https://example.com");
await appHistory.navigate("https://example.com");
MockLocation.restore();

@@ -545,3 +552,3 @@ });

await appHistory.push("/test");
await appHistory.navigate("/test");
});

@@ -573,7 +580,7 @@

// however, the promise will reject, so we need to handle that
const slowPromise = appHistory.push("/slowUrl");
const slowPromise = appHistory.navigate("/slowUrl");
slowEntry = appHistory.current;
await new Promise((resolve) => setTimeout(resolve));
await appHistory.push("/newerUrl");
await appHistory.navigate("/newerUrl");
expect(appHistory.current.url).toBe("/newerUrl");

@@ -602,3 +609,3 @@

const newUrl = "/newUrl";
await appHistory.push({ url: newUrl });
await appHistory.navigate({ url: newUrl });

@@ -620,3 +627,3 @@ expect(appHistory.current.url).toBe(newUrl);

try {
await appHistory.push({ url: newUrl });
await appHistory.navigate({ url: newUrl });
} catch (error) {}

@@ -631,3 +638,3 @@

describe("currentchange", () => {
it("should fire the currentchange event for push()", async () => {
it("should fire the currentchange event for navigate()", async () => {
// https://github.com/WICG/app-history#current-entry-change-monitoring

@@ -643,3 +650,3 @@

await appHistory.push();
await appHistory.navigate();

@@ -659,3 +666,3 @@ expect(currentEvent).toBeTruthy();

await appHistory.update({ state: "newState" });
await appHistory.navigate({ state: "newState", replace: true });

@@ -680,3 +687,3 @@ expect(currentEvent).toBeTruthy();

await appHistory.push();
await appHistory.navigate();

@@ -699,3 +706,3 @@ expect(listenerEvents).toEqual(["1", "2"]);

await appHistory.push();
await appHistory.navigate();

@@ -719,3 +726,3 @@ expect(timesCalled).toBe(1);

describe("navigateto", () => {
it("fires when the entry becomes current with 'appHistory.navigateto()'", async (done) => {
it("fires when the entry becomes current with 'appHistory.goTo()'", async (done) => {
const appHistory = new AppHistory();

@@ -728,4 +735,4 @@

await appHistory.push();
await appHistory.navigateTo(oldCurrent.key);
await appHistory.navigate();
await appHistory.goTo(oldCurrent.key);
});

@@ -741,3 +748,3 @@

await appHistory.push();
await appHistory.navigate();
await appHistory.back();

@@ -749,3 +756,3 @@ });

await appHistory.push();
await appHistory.navigate();

@@ -762,6 +769,6 @@ const oldCurrent = appHistory.current;

describe("navigatefrom", () => {
it("fires when the entry leaves current with 'appHistory.navigateto()'", async (done) => {
it("fires when the entry leaves current with 'appHistory.goTo()'", async (done) => {
const appHistory = new AppHistory();
const oldCurrent = appHistory.current;
await appHistory.push();
await appHistory.navigate();

@@ -772,6 +779,6 @@ appHistory.current.addEventListener("navigatefrom", () => {

await appHistory.navigateTo(oldCurrent.key);
await appHistory.goTo(oldCurrent.key);
});
it("fires when the entry leaves current with 'appHistory.push()'", async (done) => {
it("fires when the entry leaves current with 'appHistory.navigate()'", async (done) => {
const appHistory = new AppHistory();

@@ -783,3 +790,3 @@

await appHistory.push();
await appHistory.navigate();
});

@@ -789,3 +796,3 @@

const appHistory = new AppHistory();
await appHistory.push();
await appHistory.navigate();

@@ -802,3 +809,3 @@ appHistory.current.addEventListener("navigatefrom", () => {

await appHistory.push();
await appHistory.navigate();
await appHistory.back();

@@ -817,3 +824,3 @@

await appHistory.push();
await appHistory.navigate();

@@ -825,3 +832,3 @@ appHistory.current.addEventListener("dispose", () => {

await appHistory.back();
await appHistory.push();
await appHistory.navigate();
});

@@ -832,3 +839,3 @@

await appHistory.push();
await appHistory.navigate();

@@ -841,3 +848,3 @@ appHistory.current.addEventListener("dispose", (evt) => {

await appHistory.back();
await appHistory.push();
await appHistory.navigate();
});

@@ -858,3 +865,3 @@ });

const pushPromise = appHistory.push("/newUrl");
const pushPromise = appHistory.navigate("/newUrl");

@@ -870,7 +877,7 @@ appHistory.current.addEventListener("finish", () => {

describe("navigateTo", () => {
describe("goTo", () => {
it("should throw an exception if the key is no longer in the entries list", async () => {
const appHistory = new AppHistory();
await expect(appHistory.navigateTo("non-existent-key")).rejects.toThrow(
await expect(appHistory.goTo("non-existent-key")).rejects.toThrow(
new DOMException("InvalidStateError")

@@ -883,9 +890,9 @@ );

await appHistory.push({ url: "/test1" });
await appHistory.navigate({ url: "/test1" });
const oldKey = appHistory.current.key;
await appHistory.push({ url: "/test2" });
await appHistory.navigate({ url: "/test2" });
expect(appHistory.current.url).toBe("/test2");
await appHistory.navigateTo(oldKey);
await appHistory.goTo(oldKey);
expect(appHistory.current.url).toBe("/test1");

@@ -898,14 +905,14 @@ expect(appHistory.entries.length).toBe(3);

const appHistory = new AppHistory();
await appHistory.push();
await appHistory.push();
await appHistory.navigate();
await appHistory.navigate();
await appHistory.navigateTo(appHistory.entries[0].key);
await appHistory.goTo(appHistory.entries[0].key);
expect(appHistory.canGoBack).toBe(false);
expect(appHistory.canGoForward).toBe(true);
await appHistory.navigateTo(appHistory.entries[1].key);
await appHistory.goTo(appHistory.entries[1].key);
expect(appHistory.canGoBack).toBe(true);
expect(appHistory.canGoForward).toBe(true);
await appHistory.navigateTo(appHistory.entries[2].key);
await appHistory.goTo(appHistory.entries[2].key);
expect(appHistory.canGoBack).toBe(true);

@@ -919,3 +926,3 @@ expect(appHistory.canGoForward).toBe(false);

await appHistory.push();
await appHistory.navigate();

@@ -927,3 +934,3 @@ appHistory.addEventListener("navigate", (evt) => {

await appHistory.navigateTo(oldKey, { navigateInfo: "navigateToCalled" });
await appHistory.goTo(oldKey, { navigateInfo: "navigateToCalled" });
});

@@ -945,4 +952,4 @@ });

await appHistory.push({ url: "/test1" });
await appHistory.push({ url: "/test2" });
await appHistory.navigate({ url: "/test1" });
await appHistory.navigate({ url: "/test2" });

@@ -964,4 +971,4 @@ expect(appHistory.current.url).toBe("/test2");

const appHistory = new AppHistory();
await appHistory.push();
await appHistory.push();
await appHistory.navigate();
await appHistory.navigate();

@@ -983,3 +990,3 @@ expect(appHistory.canGoBack).toBe(true);

await appHistory.push();
await appHistory.navigate();

@@ -1008,8 +1015,8 @@ appHistory.addEventListener("navigate", (evt) => {

await appHistory.push({ url: "/test1" });
await appHistory.push({ url: "/test2" });
await appHistory.navigate({ url: "/test1" });
await appHistory.navigate({ url: "/test2" });
expect(appHistory.current.url).toBe("/test2");
await appHistory.navigateTo(firstCurrent.key);
await appHistory.goTo(firstCurrent.key);
expect(appHistory.current).toBe(firstCurrent);

@@ -1031,5 +1038,5 @@

const firstEntry = appHistory.current;
await appHistory.push();
await appHistory.push();
await appHistory.navigateTo(firstEntry.key);
await appHistory.navigate();
await appHistory.navigate();
await appHistory.goTo(firstEntry.key);

@@ -1051,3 +1058,3 @@ expect(appHistory.canGoBack).toBe(false);

await appHistory.push();
await appHistory.navigate();
await appHistory.back();

@@ -1067,3 +1074,3 @@

const appHistory = new AppHistory();
await appHistory.update({ state: "newState" });
await appHistory.navigate({ state: "newState", replace: true });
expect(appHistory.current.state).toBe(undefined);

@@ -1075,3 +1082,3 @@ expect(appHistory.current.getState()).toBe("newState");

const appHistory = new AppHistory();
await appHistory.push({ state: { test: "deep string" } });
await appHistory.navigate({ state: { test: "deep string" } });

@@ -1088,3 +1095,3 @@ const state = appHistory.current.getState();

describe("events order", () => {
it("should fire the events in order for successful push()", async () => {
it("should fire the events in order for successful navigate()", async () => {
// https://github.com/WICG/app-history#complete-event-sequence

@@ -1096,3 +1103,3 @@ const eventsList = [];

// add an entry that will be disposed of later
await appHistory.push();
await appHistory.navigate();

@@ -1132,3 +1139,3 @@ // can you add a navigateto listener to an new entry that you just pushed?

// don't await here so we can add the finish listener to the new entry; we await the promise later
const pushPromise = appHistory.push();
const pushPromise = appHistory.navigate();

@@ -1152,3 +1159,3 @@ appHistory.current.addEventListener("finish", () => {

it("should fire the events in order for unsuccessful push()", async () => {
it("should fire the events in order for unsuccessful navigate()", async () => {
// https://github.com/WICG/app-history#complete-event-sequence

@@ -1160,3 +1167,3 @@ const eventsList = [];

// add an entry that will be disposed of later
await appHistory.push();
await appHistory.navigate();

@@ -1196,3 +1203,3 @@ // can you add a navigateto listener to an new entry that you just pushed?

// don't await here so we can add the finish listener to the new entry; we await the promise later
const pushPromise = appHistory.push();
const pushPromise = appHistory.navigate();

@@ -1252,3 +1259,6 @@ appHistory.current.addEventListener("finish", () => {

// don't await here so we can add the finish listener to the new entry; we await the promise later
const updatePromise = appHistory.update({ state: "newState" });
const updatePromise = appHistory.navigate({
state: "newState",
replace: true,
});

@@ -1305,3 +1315,6 @@ appHistory.current.addEventListener("finish", () => {

// don't await here so we can add the finish listener to the new entry; we await the promise later
const updatePromise = appHistory.update({ state: "newState" });
const updatePromise = appHistory.navigate({
state: "newState",
replace: true,
});

@@ -1308,0 +1321,0 @@ appHistory.current.addEventListener("finish", () => {

@@ -26,3 +26,3 @@ import { fakeRandomId } from "./helpers";

param1?: UpdatePushParam1Types,
param2?: AppHistoryPushOrUpdateOptions
param2?: AppHistoryNavigateOptions
): AppHistoryPushOrUpdateFullOptions | undefined {

@@ -55,17 +55,31 @@ let options: AppHistoryPushOrUpdateFullOptions | undefined;

async update(
async navigate(
fullOptions?: AppHistoryPushOrUpdateFullOptions
): Promise<undefined>;
async update(
async navigate(
url?: string,
options?: AppHistoryPushOrUpdateOptions
options?: AppHistoryNavigateOptions
): Promise<undefined>;
async update(
async navigate(
param1?: UpdatePushParam1Types,
param2?: AppHistoryPushOrUpdateOptions
param2?: AppHistoryNavigateOptions
) {
const options = this.getOptionsFromParams(param1, param2);
if (options?.replace) {
return this.updateNavigation(options);
} else {
return this.pushNavigation(options);
}
}
private async updateNavigation(
options: AppHistoryPushOrUpdateFullOptions | undefined
) {
// used in currentchange event
const startTime = performance.now();
const options = this.getOptionsFromParams(param1, param2);
if (options?.replace && Object.keys(options).length === 1) {
throw new Error("Must include more options than just {'replace: true'}");
}

@@ -98,20 +112,9 @@ // location.href updates here

async push(
fullOptions?: AppHistoryPushOrUpdateFullOptions
): Promise<undefined>;
async push(
url?: string,
options?: AppHistoryPushOrUpdateOptions
): Promise<undefined>;
async push(
param1?: UpdatePushParam1Types,
param2?: AppHistoryPushOrUpdateOptions
private async pushNavigation(
options: AppHistoryPushOrUpdateFullOptions | undefined
) {
const previousEntry = this.current;
// used in the currentchange event
const startTime = performance.now();
const options = this.getOptionsFromParams(param1, param2);
const previousEntry = this.current;
const upcomingEntry = new AppHistoryEntry(options, this.current);

@@ -134,3 +137,3 @@

if (upcomingURL.origin === window.location.origin) {
window.history.pushState(null, "", upcomingEntry.url);
window.history.pushState(options?.state, "", upcomingEntry.url);
} else {

@@ -272,3 +275,3 @@ window.location.assign(upcomingEntry.url);

async navigateTo(
async goTo(
key: AppHistoryEntryKey,

@@ -334,3 +337,3 @@ navigationOptions?: AppHistoryNavigationOptions

destinationEntry: AppHistoryEntry,
info?: any
info?: unknown
): Array<Promise<undefined>> {

@@ -460,3 +463,3 @@ const respondWithResponses: Array<Promise<undefined>> = [];

index: number;
private _state: any | null;
private _state: unknown;
finished: boolean;

@@ -473,3 +476,3 @@ private latestNavigateEvent?: AppHistoryNavigateEvent;

/** Provides a JSON.parse(JSON.stringify()) copy of the Entry's state. */
getState(): any | null {
getState(): unknown {
return JSON.parse(JSON.stringify(this._state));

@@ -563,11 +566,11 @@ }

interface AppHistoryNavigationOptions {
navigateInfo?: any;
navigateInfo?: unknown;
}
interface AppHistoryPushOrUpdateOptions extends AppHistoryNavigationOptions {
state?: any | null;
interface AppHistoryNavigateOptions extends AppHistoryNavigationOptions {
state?: unknown;
replace?: boolean;
}
interface AppHistoryPushOrUpdateFullOptions
extends AppHistoryPushOrUpdateOptions {
interface AppHistoryPushOrUpdateFullOptions extends AppHistoryNavigateOptions {
url?: string;

@@ -581,3 +584,3 @@ }

formData?: null;
info: any;
info: unknown;
canRespond: boolean;

@@ -584,0 +587,0 @@ respondWith: (respondWithPromise: Promise<undefined>) => void;

@@ -20,5 +20,5 @@ import { AppHistory } from "./appHistory";

function windowClickHandler(evt: Event) {
function windowClickHandler(evt: Event): void {
if (evt.target && evt.target instanceof HTMLElement) {
// on anchor/area clicks, fire 'appHistory.push()'
// on anchor/area clicks, fire 'appHistory.navigate()'
const linkTag =

@@ -30,7 +30,12 @@ evt.target.nodeName === "A" || evt.target.nodeName === "AREA"

evt.preventDefault();
window.appHistory.push(linkTag.href).catch((err) => {
setTimeout(() => {
throw err;
window.appHistory
.navigate({
url: linkTag.href,
navigateInfo: { type: `${linkTag.nodeName.toLowerCase()}-click` },
})
.catch((err) => {
setTimeout(() => {
throw err;
});
});
});
}

@@ -37,0 +42,0 @@ }

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc