pgp-sdk 0.1.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 +58 -0
- package/dist/index.cjs +134 -0
- package/dist/index.d.cts +39 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +108 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# pgp-sdk (TypeScript)
|
|
2
|
+
|
|
3
|
+
Isomorphic (Node.js + browser) event ingestion SDK for the PGP platform.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Copy/`npm pack` from this directory (internal package, not on npm).
|
|
8
|
+
|
|
9
|
+
## Node.js
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { PgpClient } from "pgp-sdk";
|
|
13
|
+
|
|
14
|
+
const pgp = new PgpClient({ apiKey: "pgp_...", baseUrl: "https://pgp.int.wepromotion.cn" });
|
|
15
|
+
|
|
16
|
+
pgp.capture("signup", { plan: "free" });
|
|
17
|
+
pgp.setUserId("user-123");
|
|
18
|
+
pgp.capture("purchase", { amount: 99 });
|
|
19
|
+
|
|
20
|
+
await pgp.close(); // flush on shutdown
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Browser
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<script type="module">
|
|
27
|
+
import { PgpClient } from "./node_modules/pgp-sdk/dist/index.js";
|
|
28
|
+
const pgp = new PgpClient({ apiKey: "pgp_..." });
|
|
29
|
+
pgp.capture("page_view");
|
|
30
|
+
</script>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
With no `userId`, an anonymous id is generated and persisted in `localStorage`.
|
|
34
|
+
Queued events are flushed with `keepalive` on `pagehide`, so nothing is lost on navigation.
|
|
35
|
+
|
|
36
|
+
## Behavior
|
|
37
|
+
|
|
38
|
+
- Events queue in memory, flush at `flushAt` (default 20) events, every `flushInterval` ms (default 5000), or on `flush()`/`close()`.
|
|
39
|
+
- Failed requests re-queue (events are never dropped on network errors) and retry on the next flush.
|
|
40
|
+
- Concurrent `flush()` calls coalesce into one request.
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
| Option | Default | Description |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `apiKey` | — | required, from `POST /api/projects` |
|
|
47
|
+
| `baseUrl` | `https://pgp.int.wepromotion.cn` | server base URL |
|
|
48
|
+
| `userId` | anonymous id | `user_id` sent with each event |
|
|
49
|
+
| `flushAt` | `20` | queue size that triggers a flush |
|
|
50
|
+
| `flushInterval` | `5000` | ms between flushes |
|
|
51
|
+
| `fetchFn` | global `fetch` | injectable for tests |
|
|
52
|
+
|
|
53
|
+
## Dev
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm test # node:test, no server needed
|
|
57
|
+
npm run build # dist/ ESM + CJS + types
|
|
58
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
PgpClient: () => PgpClient,
|
|
24
|
+
createClient: () => createClient,
|
|
25
|
+
default: () => index_default
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var ANON_KEY = "pgp_anon_id";
|
|
29
|
+
function uuid() {
|
|
30
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
31
|
+
}
|
|
32
|
+
function anonId() {
|
|
33
|
+
try {
|
|
34
|
+
let id = localStorage.getItem(ANON_KEY);
|
|
35
|
+
if (!id) {
|
|
36
|
+
id = uuid();
|
|
37
|
+
localStorage.setItem(ANON_KEY, id);
|
|
38
|
+
}
|
|
39
|
+
return id;
|
|
40
|
+
} catch {
|
|
41
|
+
return uuid();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
var PgpClient = class {
|
|
45
|
+
#opts;
|
|
46
|
+
#fetch;
|
|
47
|
+
#queue = [];
|
|
48
|
+
#flushing = Promise.resolve();
|
|
49
|
+
#userId;
|
|
50
|
+
#timer;
|
|
51
|
+
#onHide;
|
|
52
|
+
constructor(opts) {
|
|
53
|
+
if (!opts.apiKey) throw new Error("pgp: apiKey is required");
|
|
54
|
+
this.#opts = {
|
|
55
|
+
apiKey: opts.apiKey,
|
|
56
|
+
baseUrl: (opts.baseUrl ?? "https://pgp.int.wepromotion.cn").replace(/\/$/, ""),
|
|
57
|
+
flushAt: opts.flushAt ?? 20,
|
|
58
|
+
flushInterval: opts.flushInterval ?? 5e3
|
|
59
|
+
};
|
|
60
|
+
this.#fetch = opts.fetchFn ?? fetch.bind(globalThis);
|
|
61
|
+
this.#userId = opts.userId ?? anonId();
|
|
62
|
+
if (typeof document !== "undefined") {
|
|
63
|
+
this.#onHide = () => {
|
|
64
|
+
if (this.#queue.length) void this.#flush(true);
|
|
65
|
+
};
|
|
66
|
+
document.addEventListener("visibilitychange", this.#onHide);
|
|
67
|
+
window.addEventListener("pagehide", this.#onHide);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Track the logged-in user (switches future events to this id). */
|
|
71
|
+
setUserId(userId) {
|
|
72
|
+
this.#userId = userId;
|
|
73
|
+
}
|
|
74
|
+
/** Queue an event. Flushing happens automatically. */
|
|
75
|
+
capture(name, properties) {
|
|
76
|
+
if (!this.#userId) {
|
|
77
|
+
console.error(`pgp: dropping event "${name}" \u2014 no user id`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.#queue.push({ user_id: this.#userId, name, properties });
|
|
81
|
+
if (!this.#timer) {
|
|
82
|
+
this.#timer = setInterval(() => void this.flush(), this.#opts.flushInterval);
|
|
83
|
+
this.#timer.unref?.();
|
|
84
|
+
}
|
|
85
|
+
if (this.#queue.length >= this.#opts.flushAt) void this.flush();
|
|
86
|
+
}
|
|
87
|
+
/** Flush now. Concurrent calls share one request. Never throws. */
|
|
88
|
+
flush() {
|
|
89
|
+
return this.#flushing = this.#flushing.then(() => this.#flush(false));
|
|
90
|
+
}
|
|
91
|
+
/** Flush and stop timers/listeners. Call on shutdown. */
|
|
92
|
+
async close() {
|
|
93
|
+
if (this.#timer) clearInterval(this.#timer);
|
|
94
|
+
if (this.#onHide) {
|
|
95
|
+
document.removeEventListener("visibilitychange", this.#onHide);
|
|
96
|
+
window.removeEventListener("pagehide", this.#onHide);
|
|
97
|
+
}
|
|
98
|
+
await this.flush();
|
|
99
|
+
}
|
|
100
|
+
async #flush(keepalive) {
|
|
101
|
+
if (!this.#queue.length) return;
|
|
102
|
+
const events = this.#queue;
|
|
103
|
+
this.#queue = [];
|
|
104
|
+
try {
|
|
105
|
+
const res = await this.#fetch(`${this.#opts.baseUrl}/api/events/batch`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: {
|
|
108
|
+
"content-type": "application/json",
|
|
109
|
+
authorization: `Bearer ${this.#opts.apiKey}`
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify({ events }),
|
|
112
|
+
keepalive
|
|
113
|
+
// lets the request survive page unload
|
|
114
|
+
});
|
|
115
|
+
if (!res.ok) throw new Error(`pgp: flush failed with HTTP ${res.status}`);
|
|
116
|
+
if (!this.#queue.length && this.#timer) {
|
|
117
|
+
clearInterval(this.#timer);
|
|
118
|
+
this.#timer = void 0;
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
this.#queue = events.concat(this.#queue);
|
|
122
|
+
console.error(err instanceof Error ? err.message : err);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
function createClient(opts) {
|
|
127
|
+
return new PgpClient(opts);
|
|
128
|
+
}
|
|
129
|
+
var index_default = { createClient, PgpClient };
|
|
130
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
131
|
+
0 && (module.exports = {
|
|
132
|
+
PgpClient,
|
|
133
|
+
createClient
|
|
134
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pgp-sdk — isomorphic (Node.js + browser) event ingestion SDK for the PGP platform.
|
|
3
|
+
*
|
|
4
|
+
* Batches events, flushes on `flushAt` events / `flushInterval` ms / manual `flush()`.
|
|
5
|
+
* Failed flushes are re-queued. Browsers flush with `keepalive` on pagehide.
|
|
6
|
+
*/
|
|
7
|
+
interface PgpOptions {
|
|
8
|
+
/** API key from POST /api/projects */
|
|
9
|
+
apiKey: string;
|
|
10
|
+
/** Server base URL, e.g. https://pgp.int.wepromotion.cn */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
/** Known user id; falls back to a persisted anonymous id */
|
|
13
|
+
userId?: string;
|
|
14
|
+
/** Flush when this many events are queued (default 20) */
|
|
15
|
+
flushAt?: number;
|
|
16
|
+
/** Flush at least every N ms (default 5000) */
|
|
17
|
+
flushInterval?: number;
|
|
18
|
+
/** Injectable fetch, mainly for tests */
|
|
19
|
+
fetchFn?: typeof fetch;
|
|
20
|
+
}
|
|
21
|
+
declare class PgpClient {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(opts: PgpOptions);
|
|
24
|
+
/** Track the logged-in user (switches future events to this id). */
|
|
25
|
+
setUserId(userId: string): void;
|
|
26
|
+
/** Queue an event. Flushing happens automatically. */
|
|
27
|
+
capture(name: string, properties?: Record<string, unknown>): void;
|
|
28
|
+
/** Flush now. Concurrent calls share one request. Never throws. */
|
|
29
|
+
flush(): Promise<void>;
|
|
30
|
+
/** Flush and stop timers/listeners. Call on shutdown. */
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
declare function createClient(opts: PgpOptions): PgpClient;
|
|
34
|
+
declare const _default: {
|
|
35
|
+
createClient: typeof createClient;
|
|
36
|
+
PgpClient: typeof PgpClient;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { PgpClient, type PgpOptions, createClient, _default as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pgp-sdk — isomorphic (Node.js + browser) event ingestion SDK for the PGP platform.
|
|
3
|
+
*
|
|
4
|
+
* Batches events, flushes on `flushAt` events / `flushInterval` ms / manual `flush()`.
|
|
5
|
+
* Failed flushes are re-queued. Browsers flush with `keepalive` on pagehide.
|
|
6
|
+
*/
|
|
7
|
+
interface PgpOptions {
|
|
8
|
+
/** API key from POST /api/projects */
|
|
9
|
+
apiKey: string;
|
|
10
|
+
/** Server base URL, e.g. https://pgp.int.wepromotion.cn */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
/** Known user id; falls back to a persisted anonymous id */
|
|
13
|
+
userId?: string;
|
|
14
|
+
/** Flush when this many events are queued (default 20) */
|
|
15
|
+
flushAt?: number;
|
|
16
|
+
/** Flush at least every N ms (default 5000) */
|
|
17
|
+
flushInterval?: number;
|
|
18
|
+
/** Injectable fetch, mainly for tests */
|
|
19
|
+
fetchFn?: typeof fetch;
|
|
20
|
+
}
|
|
21
|
+
declare class PgpClient {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(opts: PgpOptions);
|
|
24
|
+
/** Track the logged-in user (switches future events to this id). */
|
|
25
|
+
setUserId(userId: string): void;
|
|
26
|
+
/** Queue an event. Flushing happens automatically. */
|
|
27
|
+
capture(name: string, properties?: Record<string, unknown>): void;
|
|
28
|
+
/** Flush now. Concurrent calls share one request. Never throws. */
|
|
29
|
+
flush(): Promise<void>;
|
|
30
|
+
/** Flush and stop timers/listeners. Call on shutdown. */
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
declare function createClient(opts: PgpOptions): PgpClient;
|
|
34
|
+
declare const _default: {
|
|
35
|
+
createClient: typeof createClient;
|
|
36
|
+
PgpClient: typeof PgpClient;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { PgpClient, type PgpOptions, createClient, _default as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var ANON_KEY = "pgp_anon_id";
|
|
3
|
+
function uuid() {
|
|
4
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
5
|
+
}
|
|
6
|
+
function anonId() {
|
|
7
|
+
try {
|
|
8
|
+
let id = localStorage.getItem(ANON_KEY);
|
|
9
|
+
if (!id) {
|
|
10
|
+
id = uuid();
|
|
11
|
+
localStorage.setItem(ANON_KEY, id);
|
|
12
|
+
}
|
|
13
|
+
return id;
|
|
14
|
+
} catch {
|
|
15
|
+
return uuid();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
var PgpClient = class {
|
|
19
|
+
#opts;
|
|
20
|
+
#fetch;
|
|
21
|
+
#queue = [];
|
|
22
|
+
#flushing = Promise.resolve();
|
|
23
|
+
#userId;
|
|
24
|
+
#timer;
|
|
25
|
+
#onHide;
|
|
26
|
+
constructor(opts) {
|
|
27
|
+
if (!opts.apiKey) throw new Error("pgp: apiKey is required");
|
|
28
|
+
this.#opts = {
|
|
29
|
+
apiKey: opts.apiKey,
|
|
30
|
+
baseUrl: (opts.baseUrl ?? "https://pgp.int.wepromotion.cn").replace(/\/$/, ""),
|
|
31
|
+
flushAt: opts.flushAt ?? 20,
|
|
32
|
+
flushInterval: opts.flushInterval ?? 5e3
|
|
33
|
+
};
|
|
34
|
+
this.#fetch = opts.fetchFn ?? fetch.bind(globalThis);
|
|
35
|
+
this.#userId = opts.userId ?? anonId();
|
|
36
|
+
if (typeof document !== "undefined") {
|
|
37
|
+
this.#onHide = () => {
|
|
38
|
+
if (this.#queue.length) void this.#flush(true);
|
|
39
|
+
};
|
|
40
|
+
document.addEventListener("visibilitychange", this.#onHide);
|
|
41
|
+
window.addEventListener("pagehide", this.#onHide);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Track the logged-in user (switches future events to this id). */
|
|
45
|
+
setUserId(userId) {
|
|
46
|
+
this.#userId = userId;
|
|
47
|
+
}
|
|
48
|
+
/** Queue an event. Flushing happens automatically. */
|
|
49
|
+
capture(name, properties) {
|
|
50
|
+
if (!this.#userId) {
|
|
51
|
+
console.error(`pgp: dropping event "${name}" \u2014 no user id`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.#queue.push({ user_id: this.#userId, name, properties });
|
|
55
|
+
if (!this.#timer) {
|
|
56
|
+
this.#timer = setInterval(() => void this.flush(), this.#opts.flushInterval);
|
|
57
|
+
this.#timer.unref?.();
|
|
58
|
+
}
|
|
59
|
+
if (this.#queue.length >= this.#opts.flushAt) void this.flush();
|
|
60
|
+
}
|
|
61
|
+
/** Flush now. Concurrent calls share one request. Never throws. */
|
|
62
|
+
flush() {
|
|
63
|
+
return this.#flushing = this.#flushing.then(() => this.#flush(false));
|
|
64
|
+
}
|
|
65
|
+
/** Flush and stop timers/listeners. Call on shutdown. */
|
|
66
|
+
async close() {
|
|
67
|
+
if (this.#timer) clearInterval(this.#timer);
|
|
68
|
+
if (this.#onHide) {
|
|
69
|
+
document.removeEventListener("visibilitychange", this.#onHide);
|
|
70
|
+
window.removeEventListener("pagehide", this.#onHide);
|
|
71
|
+
}
|
|
72
|
+
await this.flush();
|
|
73
|
+
}
|
|
74
|
+
async #flush(keepalive) {
|
|
75
|
+
if (!this.#queue.length) return;
|
|
76
|
+
const events = this.#queue;
|
|
77
|
+
this.#queue = [];
|
|
78
|
+
try {
|
|
79
|
+
const res = await this.#fetch(`${this.#opts.baseUrl}/api/events/batch`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: {
|
|
82
|
+
"content-type": "application/json",
|
|
83
|
+
authorization: `Bearer ${this.#opts.apiKey}`
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({ events }),
|
|
86
|
+
keepalive
|
|
87
|
+
// lets the request survive page unload
|
|
88
|
+
});
|
|
89
|
+
if (!res.ok) throw new Error(`pgp: flush failed with HTTP ${res.status}`);
|
|
90
|
+
if (!this.#queue.length && this.#timer) {
|
|
91
|
+
clearInterval(this.#timer);
|
|
92
|
+
this.#timer = void 0;
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
this.#queue = events.concat(this.#queue);
|
|
96
|
+
console.error(err instanceof Error ? err.message : err);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
function createClient(opts) {
|
|
101
|
+
return new PgpClient(opts);
|
|
102
|
+
}
|
|
103
|
+
var index_default = { createClient, PgpClient };
|
|
104
|
+
export {
|
|
105
|
+
PgpClient,
|
|
106
|
+
createClient,
|
|
107
|
+
index_default as default
|
|
108
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pgp-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Isomorphic (Node.js + browser) event ingestion SDK for the PGP platform",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": ["dist"],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
19
|
+
"test": "node --test 'test/*.test.ts'",
|
|
20
|
+
"prepublishOnly": "npm test && npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": ["analytics", "events", "pgp"],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": { "type": "git", "url": "github:dev-artist-cn/pgp" },
|
|
25
|
+
"author": "Wei",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"tsup": "^8.5.1",
|
|
28
|
+
"typescript": "^5.9.3"
|
|
29
|
+
}
|
|
30
|
+
}
|