nuxt-visitors 1.1.1 โ†’ 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 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
- isConnected, // Ref<boolean> - Connection status
68
- isLoading, // Ref<boolean> - Loading state
69
- error, // Ref<string | null> - Error message if any
70
- reconnect // () => void - Manual reconnection
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: Tue Jan 28 2025)_
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
- declare const _default: _nuxt_schema.NuxtModule<_nuxt_schema.ModuleOptions, _nuxt_schema.ModuleOptions, false>;
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
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "nuxt-visitors",
3
3
  "configKey": "visitors",
4
- "version": "1.1.1",
4
+ "version": "1.2.0",
5
5
  "builder": {
6
- "@nuxt/module-builder": "0.8.4",
7
- "unbuild": "2.0.0"
6
+ "@nuxt/module-builder": "1.0.1",
7
+ "unbuild": "3.5.0"
8
8
  }
9
9
  }
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
- setup(_options, nuxt) {
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
- addServerHandler({
12
- route: "/.nuxt-visitors/ws",
13
- handler: resolver.resolve("./runtime/server/routes/visitors")
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,15 +10,34 @@
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
16
18
  * - reconnect: () => void - Function to manually reconnect
17
19
  */
18
20
  export declare function useVisitors(): {
19
- visitors: any;
20
- isLoading: any;
21
- error: any;
22
- isConnected: any;
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
+ }>;
39
+ isLoading: import("vue").Ref<boolean, boolean>;
40
+ error: import("vue").Ref<string | null, string | null>;
41
+ isConnected: import("vue").Ref<boolean, boolean>;
23
42
  reconnect: () => void;
24
43
  };
@@ -1,5 +1,12 @@
1
+ import { ref, onMounted, onBeforeUnmount } from "vue";
2
+ import { useState } from "#imports";
1
3
  export function useVisitors() {
2
4
  const visitors = useState("visitors", () => 0);
5
+ const locations = ref([]);
6
+ const myLocation = useState("location", () => ({
7
+ latitude: 0,
8
+ longitude: 0
9
+ }));
3
10
  const isLoading = ref(true);
4
11
  const error = ref(null);
5
12
  const wsRef = ref(null);
@@ -10,7 +17,7 @@ export function useVisitors() {
10
17
  const getWebSocketUrl = () => {
11
18
  const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
12
19
  const baseUrl = window.location.host.replace(/^(http|https):\/\//, "");
13
- return `${protocol}//${baseUrl}/.nuxt-visitors/ws`;
20
+ return `${protocol}//${baseUrl}/.nuxt-visitors/ws?latitude=${myLocation.value.latitude}&longitude=${myLocation.value.longitude}`;
14
21
  };
15
22
  const cleanup = () => {
16
23
  if (wsRef.value) {
@@ -24,7 +31,8 @@ export function useVisitors() {
24
31
  if (!isMounted.value) return;
25
32
  try {
26
33
  const data = typeof event.data === "string" ? event.data : await event.data.text();
27
- const visitorCount = parseInt(data, 10);
34
+ locations.value = JSON.parse(data);
35
+ const visitorCount = locations.value.length;
28
36
  if (!isNaN(visitorCount) && visitorCount >= 0) {
29
37
  visitors.value = visitorCount;
30
38
  } else {
@@ -92,6 +100,8 @@ export function useVisitors() {
92
100
  });
93
101
  return {
94
102
  visitors,
103
+ locations,
104
+ myLocation,
95
105
  isLoading,
96
106
  error,
97
107
  isConnected,
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, never>;
2
+ export default _default;
@@ -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
+ });
@@ -1,2 +1,2 @@
1
- declare const _default: any;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, never>;
2
2
  export default _default;
@@ -1,3 +1,4 @@
1
+ import { defineWebSocketHandler } from "h3";
1
2
  export default defineWebSocketHandler({
2
3
  open(peer) {
3
4
  peer.subscribe("nuxt-visitors");
package/dist/types.d.mts CHANGED
@@ -1,7 +1 @@
1
- import type { NuxtModule } from '@nuxt/schema'
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.1.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/types.d.ts",
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.cjs",
24
- "types": "./dist/types.d.ts",
22
+ "main": "./dist/module.mjs",
25
23
  "files": [
26
24
  "dist"
27
25
  ],
@@ -31,6 +29,7 @@
31
29
  "dev:build": "nuxi build playground",
32
30
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
33
31
  "release": "npm run lint && npm run test && npm run prepack",
32
+ "typecheck": "tsc --noEmit",
34
33
  "lint": "eslint .",
35
34
  "lint:fix": "eslint --fix .",
36
35
  "test": "vitest run",
@@ -38,23 +37,22 @@
38
37
  "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
39
38
  },
40
39
  "dependencies": {
41
- "@nuxt/kit": "^3.15.3"
40
+ "@nuxt/kit": "3.16.2",
41
+ "ufo": "^1.6.1"
42
42
  },
43
43
  "devDependencies": {
44
- "@hrcd/eslint-config": "^2.1.1",
45
- "@nuxt/devtools": "^1.7.0",
46
- "@nuxt/eslint-config": "^0.7.5",
47
- "@nuxt/module-builder": "^0.8.4",
48
- "@nuxt/schema": "^3.15.3",
49
- "@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",
50
49
  "@types/node": "latest",
51
- "changelogen": "^0.5.7",
52
- "eslint": "^9.19.0",
53
- "nuxt": "3.15.3",
54
- "typescript": "5.6.3",
55
- "vitest": "^3.0.4",
56
- "vue-tsc": "^2.2.0",
57
- "automd": "^0.3.12"
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@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0"
57
+ "packageManager": "pnpm@10.8.1"
60
58
  }
package/dist/module.cjs DELETED
@@ -1,5 +0,0 @@
1
- module.exports = function(...args) {
2
- return import('./module.mjs').then(m => m.default.call(this, ...args))
3
- }
4
- const _meta = module.exports.meta = require('./module.json')
5
- module.exports.getMeta = () => Promise.resolve(_meta)
package/dist/module.d.ts DELETED
@@ -1,5 +0,0 @@
1
- import * as _nuxt_schema from '@nuxt/schema';
2
-
3
- declare const _default: _nuxt_schema.NuxtModule<_nuxt_schema.ModuleOptions, _nuxt_schema.ModuleOptions, false>;
4
-
5
- export { _default as default };
package/dist/types.d.ts DELETED
@@ -1,7 +0,0 @@
1
- import type { NuxtModule } from '@nuxt/schema'
2
-
3
- import type { default as Module } from './module'
4
-
5
- export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
-
7
- export { default } from './module'