nuxt-visitors 1.1.2 โ 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -6
- package/dist/module.d.mts +10 -2
- package/dist/module.json +3 -3
- package/dist/module.mjs +17 -6
- package/dist/runtime/app/composables/useVisitors.d.ts +19 -0
- package/dist/runtime/app/composables/useVisitors.js +10 -2
- package/dist/runtime/app/plugins/location.server.d.ts +2 -0
- package/dist/runtime/app/plugins/location.server.js +9 -0
- package/dist/runtime/server/routes/locations.d.ts +2 -0
- package/dist/runtime/server/routes/locations.js +17 -0
- package/dist/types.d.mts +1 -7
- package/package.json +18 -20
- package/dist/module.cjs +0 -5
- package/dist/module.d.ts +0 -5
- package/dist/types.d.ts +0 -7
    
        package/README.md
    CHANGED
    
    | @@ -19,6 +19,14 @@ Add live visitor counting to your Nuxt website in seconds. WebSocket-based, type | |
| 19 19 | 
             
            - ๐งน Auto cleanup on unmount
         | 
| 20 20 | 
             
            - ๐ Leverages [Nitro WebSocket](https://nitro.unjs.io/guide/websocket) with Pub/Sub
         | 
| 21 21 |  | 
| 22 | 
            +
            > **New (Experimental):** Anonymous tracking of visitors' locations!
         | 
| 23 | 
            +
            >
         | 
| 24 | 
            +
            > When enabled, the module tracks visitor locations. The `useVisitors` composable will additionally provide:
         | 
| 25 | 
            +
            > - A `locations` array: lists locations of all visitors.
         | 
| 26 | 
            +
            > - A `myLocation` object: contains the specific geolocation data for the current visitor.
         | 
| 27 | 
            +
            >
         | 
| 28 | 
            +
            > **Note:** This feature is experimental and may be subject to future changes.
         | 
| 29 | 
            +
             | 
| 22 30 | 
             
            ## Installation
         | 
| 23 31 |  | 
| 24 32 | 
             
            Install the module to your Nuxt application with one command:
         | 
| @@ -40,6 +48,27 @@ export default defineNuxtConfig({ | |
| 40 48 | 
             
            })
         | 
| 41 49 | 
             
            ```
         | 
| 42 50 |  | 
| 51 | 
            +
            ### Configuration
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            The module accepts an optional configuration to enable location tracking. In your `nuxt.config.ts`, you can enable it as follows:
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            ```ts
         | 
| 56 | 
            +
            export default defineNuxtConfig({
         | 
| 57 | 
            +
              modules: ['nuxt-visitors'],
         | 
| 58 | 
            +
              visitors: {
         | 
| 59 | 
            +
                // Set to true to enable tracking of visitor locations
         | 
| 60 | 
            +
                locations: true
         | 
| 61 | 
            +
              },
         | 
| 62 | 
            +
              nitro: {
         | 
| 63 | 
            +
                experimental: {
         | 
| 64 | 
            +
                  websocket: true
         | 
| 65 | 
            +
                }
         | 
| 66 | 
            +
              }
         | 
| 67 | 
            +
            })
         | 
| 68 | 
            +
            ```
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            This will enable the `locations` and `myLocation` properties in the composable, as well as the `locations` property in the composable's return object.
         | 
| 71 | 
            +
             | 
| 43 72 | 
             
            ## Usage
         | 
| 44 73 |  | 
| 45 74 | 
             
            ```vue
         | 
| @@ -63,11 +92,13 @@ That's it! The module handles everything else automatically: | |
| 63 92 | 
             
            The composable provides additional features:
         | 
| 64 93 | 
             
            ```ts
         | 
| 65 94 | 
             
            const {
         | 
| 66 | 
            -
            visitors,     // Ref<number> - Current visitor count
         | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 95 | 
            +
              visitors,     // Ref<number> - Current visitor count
         | 
| 96 | 
            +
              locations,    // Ref<Location[]> - Array of geolocation objects for all visitors (if `locations: true`)
         | 
| 97 | 
            +
              myLocation,   // Ref<Location | null> - Geolocation of the current visitor (if `locations: true`)
         | 
| 98 | 
            +
              isConnected,  // Ref<boolean> - Connection status
         | 
| 99 | 
            +
              isLoading,    // Ref<boolean> - Loading state
         | 
| 100 | 
            +
              error,        // Ref<string | null> - Error message if any
         | 
| 101 | 
            +
              reconnect     // () => void - Manual reconnection
         | 
| 71 102 | 
             
            } = useVisitors()
         | 
| 72 103 | 
             
            ```
         | 
| 73 104 |  | 
| @@ -91,6 +122,20 @@ To start contributing, you can follow these steps: | |
| 91 122 |  | 
| 92 123 | 
             
            <!-- /automd -->
         | 
| 93 124 |  | 
| 125 | 
            +
            <!-- automd:fetch url="gh:hugorcd/markdown/main/src/sponsors.md" -->
         | 
| 126 | 
            +
             | 
| 127 | 
            +
            ## Sponsors
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            <p align="center">
         | 
| 130 | 
            +
              <a href="https://cdn.jsdelivr.net/gh/hugorcd/static/sponsors.svg">
         | 
| 131 | 
            +
                <img src='https://cdn.jsdelivr.net/gh/hugorcd/static/sponsors.svg' alt="HugoRCD sponsors" />
         | 
| 132 | 
            +
              </a>
         | 
| 133 | 
            +
            </p> 
         | 
| 134 | 
            +
             | 
| 135 | 
            +
            <!-- /automd -->
         | 
| 136 | 
            +
             | 
| 137 | 
            +
            ## Contributors
         | 
| 138 | 
            +
             | 
| 94 139 | 
             
            <!-- automd:contributors license=Apache author=HugoRCD github=HugoRCD/nuxt-visitors -->
         | 
| 95 140 |  | 
| 96 141 | 
             
            Published under the [APACHE](https://github.com/HugoRCD/nuxt-visitors/blob/main/LICENSE) license.
         | 
| @@ -106,6 +151,6 @@ Made by [@HugoRCD](https://github.com/HugoRCD) and [community](https://github.co | |
| 106 151 |  | 
| 107 152 | 
             
            ---
         | 
| 108 153 |  | 
| 109 | 
            -
            _๐ค auto updated with [automd](https://automd.unjs.io) (last updated:  | 
| 154 | 
            +
            _๐ค auto updated with [automd](https://automd.unjs.io) (last updated: Mon Mar 31 2025)_
         | 
| 110 155 |  | 
| 111 156 | 
             
            <!-- /automd -->
         | 
    
        package/dist/module.d.mts
    CHANGED
    
    | @@ -1,5 +1,13 @@ | |
| 1 1 | 
             
            import * as _nuxt_schema from '@nuxt/schema';
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 3 | 
            +
            type ModuleOptions = {
         | 
| 4 | 
            +
                /**
         | 
| 5 | 
            +
                 * Allows for the anonymous tracking of current visitors' locations.
         | 
| 6 | 
            +
                 * The composable `useVisitors` will provide the visitor count along with an additional `locations` array and a `myLocation` object that contains the visitors' locations.
         | 
| 7 | 
            +
                 * @default false
         | 
| 8 | 
            +
                 */
         | 
| 9 | 
            +
                locations?: boolean;
         | 
| 10 | 
            +
            };
         | 
| 11 | 
            +
            declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
         | 
| 4 12 |  | 
| 5 | 
            -
            export { _default as default };
         | 
| 13 | 
            +
            export { type ModuleOptions, _default as default };
         | 
    
        package/dist/module.json
    CHANGED
    
    
    
        package/dist/module.mjs
    CHANGED
    
    | @@ -1,17 +1,28 @@ | |
| 1 | 
            -
            import { defineNuxtModule, createResolver, addImportsDir, addServerHandler } from '@nuxt/kit';
         | 
| 1 | 
            +
            import { defineNuxtModule, createResolver, addImportsDir, addPlugin, addServerHandler } from '@nuxt/kit';
         | 
| 2 2 |  | 
| 3 3 | 
             
            const module = defineNuxtModule({
         | 
| 4 4 | 
             
              meta: {
         | 
| 5 5 | 
             
                name: "nuxt-visitors",
         | 
| 6 6 | 
             
                configKey: "visitors"
         | 
| 7 7 | 
             
              },
         | 
| 8 | 
            -
               | 
| 8 | 
            +
              defaults: {
         | 
| 9 | 
            +
                locations: false
         | 
| 10 | 
            +
              },
         | 
| 11 | 
            +
              setup(options, nuxt) {
         | 
| 9 12 | 
             
                const resolver = createResolver(import.meta.url);
         | 
| 10 13 | 
             
                addImportsDir(resolver.resolve("./runtime/app/composables"));
         | 
| 11 | 
            -
                 | 
| 12 | 
            -
                   | 
| 13 | 
            -
                   | 
| 14 | 
            -
             | 
| 14 | 
            +
                if (options.locations) {
         | 
| 15 | 
            +
                  addPlugin(resolver.resolve("./runtime/app/plugins/location.server"));
         | 
| 16 | 
            +
                  addServerHandler({
         | 
| 17 | 
            +
                    route: "/.nuxt-visitors/ws",
         | 
| 18 | 
            +
                    handler: resolver.resolve("./runtime/server/routes/locations")
         | 
| 19 | 
            +
                  });
         | 
| 20 | 
            +
                } else {
         | 
| 21 | 
            +
                  addServerHandler({
         | 
| 22 | 
            +
                    route: "/.nuxt-visitors/ws",
         | 
| 23 | 
            +
                    handler: resolver.resolve("./runtime/server/routes/visitors")
         | 
| 24 | 
            +
                  });
         | 
| 25 | 
            +
                }
         | 
| 15 26 | 
             
              }
         | 
| 16 27 | 
             
            });
         | 
| 17 28 |  | 
| @@ -10,6 +10,8 @@ | |
| 10 10 | 
             
             *
         | 
| 11 11 | 
             
             * @returns {Object} An object containing:
         | 
| 12 12 | 
             
             *  - visitors: Ref<number> - Current number of visitors
         | 
| 13 | 
            +
             *  - locations: Ref<Location[]> - Array of geolocation objects for all visitors (if `locations: true`)
         | 
| 14 | 
            +
             *  - myLocation: Ref<Location | null> - Geolocation of the current visitor (if `locations: true`)
         | 
| 13 15 | 
             
             *  - isLoading: Ref<boolean> - Loading state indicator
         | 
| 14 16 | 
             
             *  - error: Ref<string | null> - Error message if any
         | 
| 15 17 | 
             
             *  - isConnected: Ref<boolean> - WebSocket connection status
         | 
| @@ -17,6 +19,23 @@ | |
| 17 19 | 
             
             */
         | 
| 18 20 | 
             
            export declare function useVisitors(): {
         | 
| 19 21 | 
             
                visitors: import("vue").Ref<number, number>;
         | 
| 22 | 
            +
                locations: import("vue").Ref<{
         | 
| 23 | 
            +
                    latitude: number;
         | 
| 24 | 
            +
                    longitude: number;
         | 
| 25 | 
            +
                }[], {
         | 
| 26 | 
            +
                    latitude: number;
         | 
| 27 | 
            +
                    longitude: number;
         | 
| 28 | 
            +
                }[] | {
         | 
| 29 | 
            +
                    latitude: number;
         | 
| 30 | 
            +
                    longitude: number;
         | 
| 31 | 
            +
                }[]>;
         | 
| 32 | 
            +
                myLocation: import("vue").Ref<{
         | 
| 33 | 
            +
                    latitude: number;
         | 
| 34 | 
            +
                    longitude: number;
         | 
| 35 | 
            +
                }, {
         | 
| 36 | 
            +
                    latitude: number;
         | 
| 37 | 
            +
                    longitude: number;
         | 
| 38 | 
            +
                }>;
         | 
| 20 39 | 
             
                isLoading: import("vue").Ref<boolean, boolean>;
         | 
| 21 40 | 
             
                error: import("vue").Ref<string | null, string | null>;
         | 
| 22 41 | 
             
                isConnected: import("vue").Ref<boolean, boolean>;
         | 
| @@ -2,6 +2,11 @@ import { ref, onMounted, onBeforeUnmount } from "vue"; | |
| 2 2 | 
             
            import { useState } from "#imports";
         | 
| 3 3 | 
             
            export function useVisitors() {
         | 
| 4 4 | 
             
              const visitors = useState("visitors", () => 0);
         | 
| 5 | 
            +
              const locations = ref([]);
         | 
| 6 | 
            +
              const myLocation = useState("location", () => ({
         | 
| 7 | 
            +
                latitude: 0,
         | 
| 8 | 
            +
                longitude: 0
         | 
| 9 | 
            +
              }));
         | 
| 5 10 | 
             
              const isLoading = ref(true);
         | 
| 6 11 | 
             
              const error = ref(null);
         | 
| 7 12 | 
             
              const wsRef = ref(null);
         | 
| @@ -12,7 +17,7 @@ export function useVisitors() { | |
| 12 17 | 
             
              const getWebSocketUrl = () => {
         | 
| 13 18 | 
             
                const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
         | 
| 14 19 | 
             
                const baseUrl = window.location.host.replace(/^(http|https):\/\//, "");
         | 
| 15 | 
            -
                return `${protocol}//${baseUrl}/.nuxt-visitors/ws`;
         | 
| 20 | 
            +
                return `${protocol}//${baseUrl}/.nuxt-visitors/ws?latitude=${myLocation.value.latitude}&longitude=${myLocation.value.longitude}`;
         | 
| 16 21 | 
             
              };
         | 
| 17 22 | 
             
              const cleanup = () => {
         | 
| 18 23 | 
             
                if (wsRef.value) {
         | 
| @@ -26,7 +31,8 @@ export function useVisitors() { | |
| 26 31 | 
             
                if (!isMounted.value) return;
         | 
| 27 32 | 
             
                try {
         | 
| 28 33 | 
             
                  const data = typeof event.data === "string" ? event.data : await event.data.text();
         | 
| 29 | 
            -
                   | 
| 34 | 
            +
                  locations.value = JSON.parse(data);
         | 
| 35 | 
            +
                  const visitorCount = locations.value.length;
         | 
| 30 36 | 
             
                  if (!isNaN(visitorCount) && visitorCount >= 0) {
         | 
| 31 37 | 
             
                    visitors.value = visitorCount;
         | 
| 32 38 | 
             
                  } else {
         | 
| @@ -94,6 +100,8 @@ export function useVisitors() { | |
| 94 100 | 
             
              });
         | 
| 95 101 | 
             
              return {
         | 
| 96 102 | 
             
                visitors,
         | 
| 103 | 
            +
                locations,
         | 
| 104 | 
            +
                myLocation,
         | 
| 97 105 | 
             
                isLoading,
         | 
| 98 106 | 
             
                error,
         | 
| 99 107 | 
             
                isConnected,
         | 
| @@ -0,0 +1,9 @@ | |
| 1 | 
            +
            export default defineNuxtPlugin(() => {
         | 
| 2 | 
            +
              const event = useRequestEvent();
         | 
| 3 | 
            +
              useState("location", () => ({
         | 
| 4 | 
            +
                latitude: event?.context.cf?.latitude || Math.random() * 180 - 90,
         | 
| 5 | 
            +
                // default to random latitude (only in dev)
         | 
| 6 | 
            +
                longitude: event?.context.cf?.longitude || Math.random() * 360 - 180
         | 
| 7 | 
            +
                // default to random longitude (only in dev)
         | 
| 8 | 
            +
              }));
         | 
| 9 | 
            +
            });
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            import { defineWebSocketHandler } from "h3";
         | 
| 2 | 
            +
            import { getQuery } from "ufo";
         | 
| 3 | 
            +
            export default defineWebSocketHandler({
         | 
| 4 | 
            +
              open(peer) {
         | 
| 5 | 
            +
                const locations = Array.from(peer.peers.values()).map((peer2) => getQuery(peer2.websocket.url));
         | 
| 6 | 
            +
                peer.subscribe("nuxt-visitors");
         | 
| 7 | 
            +
                peer.publish("nuxt-visitors", JSON.stringify(locations));
         | 
| 8 | 
            +
                peer.send(JSON.stringify(locations));
         | 
| 9 | 
            +
              },
         | 
| 10 | 
            +
              close(peer) {
         | 
| 11 | 
            +
                peer.unsubscribe("nuxt-visitors");
         | 
| 12 | 
            +
                setTimeout(() => {
         | 
| 13 | 
            +
                  const locations = Array.from(peer.peers.values()).map((peer2) => getQuery(peer2.websocket.url));
         | 
| 14 | 
            +
                  peer.publish("nuxt-visitors", JSON.stringify(locations));
         | 
| 15 | 
            +
                }, 500);
         | 
| 16 | 
            +
              }
         | 
| 17 | 
            +
            });
         | 
    
        package/dist/types.d.mts
    CHANGED
    
    | @@ -1,7 +1 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
            import type { default as Module } from './module.js'
         | 
| 4 | 
            -
             | 
| 5 | 
            -
            export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            export { default } from './module.js'
         | 
| 1 | 
            +
            export { type ModuleOptions, default } from './module.mjs'
         | 
    
        package/package.json
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "nuxt-visitors",
         | 
| 3 | 
            -
              "version": "1. | 
| 3 | 
            +
              "version": "1.2.0",
         | 
| 4 4 | 
             
              "description": "Add real-time visitor tracking to your Nuxt app with one line of code. WebSocket made easy",
         | 
| 5 5 | 
             
              "license": "Apache-2.0",
         | 
| 6 6 | 
             
              "type": "module",
         | 
| @@ -15,13 +15,11 @@ | |
| 15 15 | 
             
              "author": "HugoRCD",
         | 
| 16 16 | 
             
              "exports": {
         | 
| 17 17 | 
             
                ".": {
         | 
| 18 | 
            -
                  "types": "./dist/ | 
| 19 | 
            -
                  "import": "./dist/module.mjs" | 
| 20 | 
            -
                  "require": "./dist/module.cjs"
         | 
| 18 | 
            +
                  "types": "./dist/module.d.mts",
         | 
| 19 | 
            +
                  "import": "./dist/module.mjs"
         | 
| 21 20 | 
             
                }
         | 
| 22 21 | 
             
              },
         | 
| 23 | 
            -
              "main": "./dist/module. | 
| 24 | 
            -
              "types": "./dist/types.d.ts",
         | 
| 22 | 
            +
              "main": "./dist/module.mjs",
         | 
| 25 23 | 
             
              "files": [
         | 
| 26 24 | 
             
                "dist"
         | 
| 27 25 | 
             
              ],
         | 
| @@ -39,22 +37,22 @@ | |
| 39 37 | 
             
                "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
         | 
| 40 38 | 
             
              },
         | 
| 41 39 | 
             
              "dependencies": {
         | 
| 42 | 
            -
                "@nuxt/kit": "3. | 
| 40 | 
            +
                "@nuxt/kit": "3.16.2",
         | 
| 41 | 
            +
                "ufo": "^1.6.1"
         | 
| 43 42 | 
             
              },
         | 
| 44 43 | 
             
              "devDependencies": {
         | 
| 45 | 
            -
                "@hrcd/eslint-config": " | 
| 46 | 
            -
                "@nuxt/devtools": "^ | 
| 47 | 
            -
                "@nuxt/ | 
| 48 | 
            -
                "@nuxt/ | 
| 49 | 
            -
                "@nuxt/ | 
| 50 | 
            -
                "@nuxt/test-utils": "^3.15.4",
         | 
| 44 | 
            +
                "@hrcd/eslint-config": "edge",
         | 
| 45 | 
            +
                "@nuxt/devtools": "^2.4.0",
         | 
| 46 | 
            +
                "@nuxt/module-builder": "^1.0.1",
         | 
| 47 | 
            +
                "@nuxt/schema": "3.16.2",
         | 
| 48 | 
            +
                "@nuxt/test-utils": "^3.17.2",
         | 
| 51 49 | 
             
                "@types/node": "latest",
         | 
| 52 | 
            -
                "automd": "^0. | 
| 53 | 
            -
                "eslint": "^9. | 
| 54 | 
            -
                "nuxt": "3. | 
| 55 | 
            -
                "typescript": "5. | 
| 56 | 
            -
                "vitest": "^3. | 
| 57 | 
            -
                "vue-tsc": "^2.2. | 
| 50 | 
            +
                "automd": "^0.4.0",
         | 
| 51 | 
            +
                "eslint": "^9.24.0",
         | 
| 52 | 
            +
                "nuxt": "3.16.0",
         | 
| 53 | 
            +
                "typescript": "5.8.3",
         | 
| 54 | 
            +
                "vitest": "^3.1.1",
         | 
| 55 | 
            +
                "vue-tsc": "^2.2.8"
         | 
| 58 56 | 
             
              },
         | 
| 59 | 
            -
              "packageManager": "pnpm@ | 
| 57 | 
            +
              "packageManager": "pnpm@10.8.1"
         | 
| 60 58 | 
             
            }
         | 
    
        package/dist/module.cjs
    DELETED
    
    
    
        package/dist/module.d.ts
    DELETED
    
    
    
        package/dist/types.d.ts
    DELETED