npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

last-open

v1.0.0

Published

Track last visit times with support for React, Vue, and Angular using unstorage

Downloads

4

Readme

last-open

Track last visit times with support for React, Vue, and Angular using unstorage.

Features

  • 🔄 Cross-framework: Works with React, Vue, Angular, and vanilla JavaScript
  • 💾 Flexible storage: Uses unstorage with localStorage by default, supports any storage backend
  • ⚙️ Configurable: Custom keys, key factories, and storage instances
  • 📦 Lightweight: Minimal dependencies, tree-shakeable
  • 🔒 Type-safe: Written in TypeScript with full type definitions
  • Well-tested: Comprehensive test coverage

Installation

npm install last-open unstorage
# or
pnpm add last-open unstorage
# or
yarn add last-open unstorage

Usage

Vanilla JavaScript/TypeScript

import { initLastOpen, getLastOpen, getTimeSinceLastOpen } from 'last-open'

// Initialize on app start - records current timestamp
await initLastOpen()

// Get the last open timestamp
const lastOpen = await getLastOpen()
console.log('Last opened at:', new Date(lastOpen))

// Get time since last open (in milliseconds)
const timeSince = await getTimeSinceLastOpen()
console.log('Time since last open:', timeSince, 'ms')

React

import { useLastOpen } from 'last-open/react'

function App() {
  const { lastOpen, timeSince, isLoading, error } = useLastOpen()

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>

  return (
    <div>
      <h1>Welcome back!</h1>
      {lastOpen && <p>Last visit: {new Date(lastOpen).toLocaleString()}</p>}
      {timeSince && (
        <p>Time since last visit: {Math.floor(timeSince / 1000)}s ago</p>
      )}
    </div>
  )
}

With custom configuration:

import { createStorage } from 'unstorage'
import indexedDbDriver from 'unstorage/drivers/indexeddb'
import { useLastOpen } from 'last-open/react'

const customStorage = createStorage({
  driver: indexedDbDriver({ base: 'myapp:' }),
})

function UserDashboard({ userId }) {
  const { lastOpen, timeSince, refresh } = useLastOpen({
    storage: customStorage,
    key: () => `user:${userId}:last-visit`, // Dynamic key per user
  })

  return (
    <div>
      <h2>Welcome back, User {userId}!</h2>
      {lastOpen && <p>Last visit: {new Date(lastOpen).toLocaleString()}</p>}
      <button onClick={refresh}>Refresh</button>
    </div>
  )
}

Or use the provider component:

import { LastOpenProvider } from 'last-open/react'

function App() {
  return (
    <LastOpenProvider>
      <YourApp />
    </LastOpenProvider>
  )
}

With custom storage:

import { createStorage } from 'unstorage'
import indexedDbDriver from 'unstorage/drivers/indexeddb'
import { LastOpenProvider } from 'last-open/react'

const customStorage = createStorage({
  driver: indexedDbDriver({ base: 'myapp:' }),
})

function App() {
  return (
    <LastOpenProvider
      config={{
        storage: customStorage,
        key: 'my-last-visit',
      }}
    >
      <YourApp />
    </LastOpenProvider>
  )
}

Vue 3

<script setup>
import { useLastOpen } from 'last-open/vue'

const { lastOpen, timeSince, isLoading, error } = useLastOpen()
</script>

<template>
  <div>
    <div v-if="isLoading">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <div v-else>
      <h1>Welcome back!</h1>
      <p v-if="lastOpen">
        Last visit: {{ new Date(lastOpen).toLocaleString() }}
      </p>
      <p v-if="timeSince">
        Time since last visit: {{ Math.floor(timeSince / 1000) }}s ago
      </p>
    </div>
  </div>
</template>

With custom configuration:

<script setup>
import { createStorage } from 'unstorage'
import indexedDbDriver from 'unstorage/drivers/indexeddb'
import { useLastOpen } from 'last-open/vue'

const customStorage = createStorage({
  driver: indexedDbDriver({ base: 'myapp:' }),
})

const props = defineProps<{ userId: string }>()

const { lastOpen, timeSince, refresh, clear } = useLastOpen({
  storage: customStorage,
  key: () => `user:${props.userId}:last-visit`,
})
</script>

<template>
  <div>
    <h2>Welcome back, User {{ userId }}!</h2>
    <p v-if="lastOpen">Last visit: {{ new Date(lastOpen).toLocaleString() }}</p>
    <button @click="refresh">Refresh</button>
    <button @click="clear">Clear History</button>
  </div>
</template>

Or use the plugin:

import { createApp } from 'vue'
import { LastOpenPlugin } from 'last-open/vue'
import App from './App.vue'

const app = createApp(App)
app.use(LastOpenPlugin)
app.mount('#app')

With custom configuration for the plugin:

import { createApp } from 'vue'
import { createStorage } from 'unstorage'
import indexedDbDriver from 'unstorage/drivers/indexeddb'
import { LastOpenPlugin } from 'last-open/vue'
import App from './App.vue'

const customStorage = createStorage({
  driver: indexedDbDriver({ base: 'myapp:' }),
})

const app = createApp(App)
app.use(LastOpenPlugin, {
  storage: customStorage,
  key: 'my-last-visit',
})
app.mount('#app')

Angular

import { Component, OnInit } from '@angular/core'
import { LastOpenService } from 'last-open/angular'

@Component({
  selector: 'app-root',
  template: `
    <div *ngIf="lastOpenService.isLoading">Loading...</div>
    <div *ngIf="lastOpenService.error">
      Error: {{ lastOpenService.error.message }}
    </div>
    <div *ngIf="!lastOpenService.isLoading && !lastOpenService.error">
      <h1>Welcome back!</h1>
      <p *ngIf="lastOpenService.lastOpen">
        Last visit: {{ lastOpenService.lastOpen | date: 'medium' }}
      </p>
      <p *ngIf="lastOpenService.timeSince">
        Time since last visit:
        {{ lastOpenService.timeSince / 1000 | number: '1.0-0' }}s ago
      </p>
    </div>
  `,
})
export class AppComponent implements OnInit {
  constructor(public lastOpenService: LastOpenService) {}

  ngOnInit() {
    // Service automatically initializes
  }
}

With custom configuration:

import { Component, OnInit } from '@angular/core'
import { createStorage } from 'unstorage'
import indexedDbDriver from 'unstorage/drivers/indexeddb'
import { LastOpenService } from 'last-open/angular'

const customStorage = createStorage({
  driver: indexedDbDriver({ base: 'myapp:' }),
})

@Component({
  selector: 'app-dashboard',
  template: `
    <div>
      <h2>User Dashboard</h2>
      <p *ngIf="lastOpenService.lastOpen">
        Last visit: {{ lastOpenService.lastOpen | date: 'medium' }}
      </p>
      <button (click)="lastOpenService.refresh()">Refresh</button>
      <button (click)="lastOpenService.clear()">Clear History</button>
    </div>
  `,
})
export class DashboardComponent implements OnInit {
  constructor(public lastOpenService: LastOpenService) {}

  ngOnInit() {
    // Initialize with custom config
    this.lastOpenService.initWithConfig({
      storage: customStorage,
      key: () => `user:${this.getUserId()}:last-visit`,
    })
  }

  private getUserId(): string {
    // Get user ID from auth service or route params
    return 'user123'
  }
}

Configuration

Custom Storage Backend

import { createStorage } from 'unstorage'
import indexedDbDriver from 'unstorage/drivers/indexeddb'
import { initLastOpen } from 'last-open'

const storage = createStorage({
  driver: indexedDbDriver({ base: 'app:' }),
})

await initLastOpen({ storage })

Custom Key

import { initLastOpen } from 'last-open'

// Static key
await initLastOpen({ key: 'my-custom-key' })

// Dynamic key (e.g., per-user)
await initLastOpen({
  key: () => `last-open:${getCurrentUserId()}`,
})

Framework-Specific Configuration

React

const { lastOpen } = useLastOpen({
  storage: myCustomStorage,
  key: 'custom-key',
})

Vue

const { lastOpen } = useLastOpen({
  storage: myCustomStorage,
  key: () => `user:${userId}:last-open`,
})

Angular

constructor(private lastOpenService: LastOpenService) {
  this.lastOpenService.initWithConfig({
    storage: myCustomStorage,
    key: 'custom-key'
  })
}

API

Core API

createLastOpenTracker(config?)

Creates a tracker instance with methods to manage last open timestamps.

const tracker = createLastOpenTracker({ key: 'my-key' })
await tracker.init()
const lastOpen = await tracker.getLastOpen()

initLastOpen(config?)

Initialize and record current timestamp.

getLastOpen(config?)

Get the last open timestamp (milliseconds since epoch).

getTimeSinceLastOpen(config?)

Get time elapsed since last open (in milliseconds).

clearLastOpen(config?)

Clear the stored timestamp.

React API

useLastOpen(config?)

Hook that returns:

  • lastOpen: Last open timestamp
  • timeSince: Time since last open (ms)
  • isLoading: Loading state
  • error: Error if any
  • refresh(): Manually refresh values
  • clear(): Clear stored timestamp

<LastOpenProvider>

Component that automatically initializes tracking on mount.

Vue API

useLastOpen(config?)

Composable that returns reactive refs:

  • lastOpen: Last open timestamp
  • timeSince: Time since last open (ms)
  • isLoading: Loading state
  • error: Error if any
  • refresh(): Manually refresh values
  • clear(): Clear stored timestamp

LastOpenPlugin

Vue plugin that initializes tracking when the app mounts.

Angular API

LastOpenService

Injectable service with:

  • lastOpen: Last open timestamp (getter)
  • timeSince: Time since last open (getter)
  • isLoading: Loading state (getter)
  • error: Error if any (getter)
  • refresh(): Manually refresh values
  • clear(): Clear stored timestamp
  • initWithConfig(config): Initialize with custom config

TypeScript

Full TypeScript support with exported types:

import type { LastOpenConfig, LastOpenTracker, Storage } from 'last-open'

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.