@pmate/log-sdk 0.5.0 → 0.6.1
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 +47 -2
- package/dist/errors.js +18 -0
- package/dist/web.d.ts +35 -0
- package/dist/web.js +56 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,12 +1,57 @@
|
|
|
1
1
|
# @pmate/log-sdk
|
|
2
2
|
|
|
3
|
+
## Web: initialize once, then report user actions
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { web } from '@pmate/log-sdk/web'
|
|
7
|
+
|
|
8
|
+
web.init({
|
|
9
|
+
key: 'KEY_FROM_DEVELOPERS',
|
|
10
|
+
appId: 'pmate:hello',
|
|
11
|
+
auto: { pv: true, webVitals: true }, // defaults to both enabled
|
|
12
|
+
})
|
|
13
|
+
web.track('signup.clicked', { entry: 'home' })
|
|
14
|
+
web.identify('profile-alice') // optional self-reported profile selection
|
|
15
|
+
web.reset() // clear profile on logout; preserve the anonymous visit
|
|
16
|
+
await web.flush()
|
|
17
|
+
// await web.close() on application teardown, not on every route
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Create a Web key with `pv`, `behavior` and `vitals` at https://dev.pmate.chat/log-keys.
|
|
21
|
+
Existing keys retain their original scopes. If your key excludes a topic, disable
|
|
22
|
+
its automatic collection. `web.pageView()` records a manual visit; turn off
|
|
23
|
+
`auto.pv` when your router explicitly records visits to avoid double counting.
|
|
24
|
+
The SDK strips URL query strings/fragments and records available CLS, FCP, INP,
|
|
25
|
+
LCP and TTFB separately from PV. SPA changes never move delayed document metrics
|
|
26
|
+
to a new route. Metrics remain runtime logs (48h); user events remain event logs (1y).
|
|
27
|
+
|
|
28
|
+
`init` is synchronous local configuration; it makes no registration or login request.
|
|
29
|
+
The default endpoint is `https://log-api.pmate.chat`; an optional `endpoint` supports
|
|
30
|
+
another deployment (HTTPS, or HTTP localhost for tests). Same-configuration init
|
|
31
|
+
is idempotent, changed configuration requires awaiting `close()`. Pre-init calls
|
|
32
|
+
are ignored with a one-time warning. Importing is SSR-safe; initialize in a client
|
|
33
|
+
lifecycle. `createWebLogger()` provides an isolated instance for tests or multiple
|
|
34
|
+
independent applications; normally use the singleton `web`.
|
|
35
|
+
|
|
36
|
+
Properties/identity are captured at event time. `identify` does not sign in a user,
|
|
37
|
+
and never takes an auth token. The in-memory queue holds 200 entries by default,
|
|
38
|
+
batches at most 50 entries/48 KiB, attempts an entry at most 3 times using the
|
|
39
|
+
original ID/time, and flushes every 5 seconds and on hide. `flush` drains all topics;
|
|
40
|
+
`close` stops listeners/admission and drains. Delivery is best effort; no disk queue.
|
|
41
|
+
Use explicit behavior events; do not send personal data in properties or path names.
|
|
42
|
+
|
|
43
|
+
Native Swift/Kotlin SDKs in `parrot-native` expose the equivalent
|
|
44
|
+
`PmateLog.initSDK`, `screen`, `track`, `identify`, `reset`, `flush`, `close` API,
|
|
45
|
+
with automatic app foreground/background events. Their examples are generated by
|
|
46
|
+
the Key console for the selected platform.
|
|
47
|
+
|
|
3
48
|
## Background
|
|
4
49
|
PMate logs need to connect anonymous page visits, signed-in Profiles and backend requests without merging different Profiles of one Account. This SDK adds access/profile context to the existing topic log protocol. It reuses `@pmate/log` for browser delivery and supports the same ingest protocol or a trusted Store sink on Node.
|
|
5
50
|
|
|
6
51
|
## Runtime and Event APIs
|
|
7
52
|
|
|
8
53
|
Classification follows event meaning, not the runtime or the presence of HTTP.
|
|
9
|
-
The
|
|
54
|
+
The lower-level browser/React APIs expose separate runtime/event scopes. The Web scenario entry provides direct pageView/track methods.
|
|
10
55
|
|
|
11
56
|
| API | Meaning | Chain | Retention |
|
|
12
57
|
| --- | --- | --- | --- |
|
|
@@ -48,7 +93,7 @@ await log.flush()
|
|
|
48
93
|
|
|
49
94
|
For native JavaScript clients, use `@pmate/log-sdk/event` with the same options plus
|
|
50
95
|
`platform: 'ios'` or `'android'`. It has no browser/Node dependencies; call
|
|
51
|
-
`log.event.pageView('/screen')` explicitly.
|
|
96
|
+
`log.event.pageView('/screen')` explicitly. Custom clients can POST
|
|
52
97
|
`{category:'event',topic:'behavior',items:[{id,time,data:{appId,platform,event}}]}`
|
|
53
98
|
to `/event/logs/batch` with `x-pmate-log-key`. IDs and timestamps must be retained
|
|
54
99
|
on retries. Profile/access IDs are optional untrusted client claims.
|
package/dist/errors.js
CHANGED
|
@@ -11,12 +11,30 @@ export function errorInput(error, context = {}) {
|
|
|
11
11
|
}
|
|
12
12
|
}).slice(0, 4000);
|
|
13
13
|
const value = error instanceof Error ? error : new Error("Non-Error exception");
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
const describe = (input, depth = 0) => {
|
|
16
|
+
if (!input || typeof input !== "object" || depth >= 3 || seen.has(input))
|
|
17
|
+
return undefined;
|
|
18
|
+
seen.add(input);
|
|
19
|
+
const item = input;
|
|
20
|
+
const diagnostic = {};
|
|
21
|
+
for (const key of ["source", "stage", "database", "tables", "mode", "attempt"]) {
|
|
22
|
+
const entry = item.diagnostic?.[key];
|
|
23
|
+
if (typeof entry === "string")
|
|
24
|
+
diagnostic[key] = clean(entry);
|
|
25
|
+
else if (typeof entry === "number" && Number.isFinite(entry))
|
|
26
|
+
diagnostic[key] = entry;
|
|
27
|
+
}
|
|
28
|
+
return { name: clean(item.name ?? "Error"), message: clean(item.message ?? ""), ...(item.stack ? { stack: clean(item.stack) } : {}), ...(Object.keys(diagnostic).length ? { diagnostic } : {}), ...(item.cause ? { cause: describe(item.cause, depth + 1) } : {}), ...(item.inner ? { inner: describe(item.inner, depth + 1) } : {}) };
|
|
29
|
+
};
|
|
30
|
+
const details = describe(value);
|
|
14
31
|
return { data: {
|
|
15
32
|
event: context.fatal ? "runtime.crash" : "runtime.error",
|
|
16
33
|
errorName: clean(value.name), message: clean(value.message),
|
|
17
34
|
...(value.stack ? { stack: clean(value.stack) } : {}),
|
|
18
35
|
...(context.operation ? { operation: context.operation } : {}),
|
|
19
36
|
...(context.componentStack ? { componentStack: clean(context.componentStack) } : {}),
|
|
37
|
+
...(details ? { errorDetails: details } : {}),
|
|
20
38
|
fatal: context.fatal ?? false,
|
|
21
39
|
} };
|
|
22
40
|
}
|
package/dist/web.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type WebLogOptions = {
|
|
2
|
+
key: string;
|
|
3
|
+
appId: string;
|
|
4
|
+
endpoint?: string;
|
|
5
|
+
/** Automatic page visits (including SPA navigation) and document metrics. Both default to true. */
|
|
6
|
+
auto?: {
|
|
7
|
+
pv?: boolean;
|
|
8
|
+
webVitals?: boolean;
|
|
9
|
+
};
|
|
10
|
+
flushIntervalMs?: number;
|
|
11
|
+
maxQueue?: number;
|
|
12
|
+
};
|
|
13
|
+
/** One lifecycle per browser application. Importing this entry is safe during SSR. */
|
|
14
|
+
export declare function createWebLogger(): {
|
|
15
|
+
init(options: WebLogOptions): /*elided*/ any;
|
|
16
|
+
pageView(): void;
|
|
17
|
+
track(name: string, properties?: Record<string, unknown>): void;
|
|
18
|
+
/** A self-reported profile ID, never a login credential. Applies only to future events. */
|
|
19
|
+
identify(profileId: string): void;
|
|
20
|
+
/** Clear profile selection on logout; the anonymous visit remains continuous. */
|
|
21
|
+
reset(): void;
|
|
22
|
+
flush(): Promise<void>;
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
export declare const web: {
|
|
26
|
+
init(options: WebLogOptions): /*elided*/ any;
|
|
27
|
+
pageView(): void;
|
|
28
|
+
track(name: string, properties?: Record<string, unknown>): void;
|
|
29
|
+
/** A self-reported profile ID, never a login credential. Applies only to future events. */
|
|
30
|
+
identify(profileId: string): void;
|
|
31
|
+
/** Clear profile selection on logout; the anonymous visit remains continuous. */
|
|
32
|
+
reset(): void;
|
|
33
|
+
flush(): Promise<void>;
|
|
34
|
+
close(): Promise<void>;
|
|
35
|
+
};
|
package/dist/web.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createLogger } from "./browser.js";
|
|
2
|
+
const defaults = (options) => ({
|
|
3
|
+
key: options.key, appId: options.appId, endpoint: options.endpoint ?? "https://log-api.pmate.chat",
|
|
4
|
+
auto: { pv: options.auto?.pv ?? true, webVitals: options.auto?.webVitals ?? true },
|
|
5
|
+
flushIntervalMs: options.flushIntervalMs ?? 5000, maxQueue: options.maxQueue ?? 200,
|
|
6
|
+
});
|
|
7
|
+
/** One lifecycle per browser application. Importing this entry is safe during SSR. */
|
|
8
|
+
export function createWebLogger() {
|
|
9
|
+
let logger;
|
|
10
|
+
let configuration, closing, warned = false;
|
|
11
|
+
const current = () => {
|
|
12
|
+
if (!logger && !warned) {
|
|
13
|
+
warned = true;
|
|
14
|
+
console.warn("PMate web log: call init before reporting events.");
|
|
15
|
+
}
|
|
16
|
+
return logger;
|
|
17
|
+
};
|
|
18
|
+
const api = {
|
|
19
|
+
init(options) {
|
|
20
|
+
const config = defaults(options), signature = JSON.stringify(config);
|
|
21
|
+
if (closing)
|
|
22
|
+
throw Error("Await web.close() before initializing again");
|
|
23
|
+
if (logger) {
|
|
24
|
+
if (configuration !== signature)
|
|
25
|
+
throw Error("Web log is already initialized; close before changing configuration");
|
|
26
|
+
return api;
|
|
27
|
+
}
|
|
28
|
+
logger = createLogger({ endpoint: config.endpoint, eventKey: config.key, appId: config.appId,
|
|
29
|
+
pageViews: config.auto.pv, vitals: config.auto.webVitals, errors: false,
|
|
30
|
+
flushIntervalMs: config.flushIntervalMs, maxQueue: config.maxQueue });
|
|
31
|
+
configuration = signature;
|
|
32
|
+
warned = false;
|
|
33
|
+
return api;
|
|
34
|
+
},
|
|
35
|
+
pageView() { current()?.event.pageView(); },
|
|
36
|
+
track(name, properties = {}) { current()?.event.track(name, properties); },
|
|
37
|
+
/** A self-reported profile ID, never a login credential. Applies only to future events. */
|
|
38
|
+
identify(profileId) { current()?.setProfileId(profileId); },
|
|
39
|
+
/** Clear profile selection on logout; the anonymous visit remains continuous. */
|
|
40
|
+
reset() { current()?.setProfileId(null); },
|
|
41
|
+
flush() { return current()?.flush() ?? Promise.resolve(); },
|
|
42
|
+
close() {
|
|
43
|
+
if (closing)
|
|
44
|
+
return closing;
|
|
45
|
+
const previous = logger;
|
|
46
|
+
logger = undefined;
|
|
47
|
+
configuration = undefined;
|
|
48
|
+
if (!previous)
|
|
49
|
+
return Promise.resolve();
|
|
50
|
+
closing = previous.close().finally(() => { closing = undefined; });
|
|
51
|
+
return closing;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
return api;
|
|
55
|
+
}
|
|
56
|
+
export const web = createWebLogger();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pmate/log-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "PMate browser, React and Node logs with access and profile context",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -29,10 +29,14 @@
|
|
|
29
29
|
"./event": {
|
|
30
30
|
"types": "./dist/event.d.ts",
|
|
31
31
|
"import": "./dist/event.js"
|
|
32
|
+
},
|
|
33
|
+
"./web": {
|
|
34
|
+
"types": "./dist/web.d.ts",
|
|
35
|
+
"import": "./dist/web.js"
|
|
32
36
|
}
|
|
33
37
|
},
|
|
34
38
|
"dependencies": {
|
|
35
|
-
"@pmate/log": "0.3.
|
|
39
|
+
"@pmate/log": "0.3.1"
|
|
36
40
|
},
|
|
37
41
|
"peerDependencies": {
|
|
38
42
|
"react": ">=18"
|