@webiny/telemetry 0.0.0-unstable.b14eaecf38 β†’ 0.0.0-unstable.b6d7105cee

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
@@ -1 +1,11 @@
1
1
  # @webiny/telemetry
2
+
3
+ > [!NOTE]
4
+ > This package is part of the [Webiny](https://www.webiny.com) monorepo.
5
+ > It’s **included in every Webiny project by default** and is not meant to be used as a standalone package.
6
+
7
+ πŸ“˜ **Documentation:** [https://www.webiny.com/docs](https://www.webiny.com/docs)
8
+
9
+ ---
10
+
11
+ _This README file is automatically generated during the publish process._
package/cli.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export declare interface SendEventParams {
2
+ event: string;
3
+ version?: string;
4
+ properties: Record<string, any>;
5
+ }
6
+
7
+ export declare function sendEvent(params: SendEventParams): Promise<void>;
8
+
9
+ export declare function isEnabled(): boolean;
10
+ export declare function enable(): boolean;
11
+ export declare function disable(): boolean;
package/cli.js CHANGED
@@ -1,40 +1,89 @@
1
- const createSendEvent = require("./sendEvent");
2
- const { globalConfig } = require("@webiny/global-config");
1
+ import { globalConfig } from "@webiny/global-config";
2
+ import { isCI } from "ci-info";
3
+ import { WTS } from "@webiny/wts-client/node";
4
+ import baseSendEvent from "./sendEvent.js";
5
+ import { loadJsonFileSync } from "load-json-file";
6
+ import path from "path";
3
7
 
4
- const sendEvent = ({ event, user, version, properties, extraPayload }) => {
8
+ export const sendEvent = async ({ event, version, properties }) => {
5
9
  const shouldSend = isEnabled();
10
+ if (!shouldSend) {
11
+ return;
12
+ }
13
+
14
+ // Use the canonical Webiny machine id β€” the top-level `id` field in
15
+ // `~/.webiny/config`, owned by @webiny/global-config. The admin app
16
+ // (REACT_APP_WEBINY_TELEMETRY_USER_ID) and the website install/finish alias
17
+ // both key off this same id, so passing it here keeps CLI, admin, and
18
+ // website events on a single PostHog person. Without it, the WTS client
19
+ // falls back to its own `user.id` field, which is a different UUID and
20
+ // fragments funnels across surfaces.
21
+ const wts = new WTS({ source: "cli", distinctId: globalConfig.get("id") });
22
+
23
+ const wcpProperties = {};
24
+ const [wcpOrgId, wcpProjectId] = getWcpOrgProjectId();
25
+ if (wcpOrgId && wcpProjectId) {
26
+ wcpProperties.wcpOrgId = wcpOrgId;
27
+ wcpProperties.wcpProjectId = wcpProjectId;
28
+ }
29
+
30
+ const installationProperties = {};
31
+ const installationId = getInstallationId();
32
+ if (installationId) {
33
+ installationProperties.installation_id = installationId;
34
+ }
35
+
36
+ const packageJsonPath = path.join(import.meta.dirname, "package.json");
37
+ const packageJson = loadJsonFileSync(packageJsonPath);
6
38
 
39
+ return baseSendEvent({
40
+ event,
41
+ properties: {
42
+ ...properties,
43
+ ...wcpProperties,
44
+ ...installationProperties,
45
+ version: version || packageJson.version,
46
+ ci: isCI,
47
+ newUser: Boolean(globalConfig.get("newUser"))
48
+ },
49
+ wts
50
+ });
51
+ };
52
+
53
+ const getWcpOrgProjectId = () => {
54
+ // In CLI, project ID is stored in the `WEBINY_PROJECT_ID` or `WCP_PROJECT_ID` environment variable.
55
+ const id = process.env.WEBINY_PROJECT_ID || process.env.WCP_PROJECT_ID;
56
+ if (typeof id === "string") {
57
+ return id.split("/");
58
+ }
59
+ return [];
60
+ };
61
+
62
+ /**
63
+ * Reads the anonymous per-project installation id from
64
+ * `<project-root>/package.json` β†’ `webiny.installationId`. Generated once at
65
+ * `create-webiny-project` time and tracked in git so it stays stable across
66
+ * machines that share the project. Returns null if the file is missing or
67
+ * unreadable β€” telemetry events still fire, just without the property.
68
+ */
69
+ const getInstallationId = () => {
7
70
  try {
8
- const sendTelemetry = createSendEvent({
9
- event,
10
- user: user || globalConfig.get("id"),
11
- version: version || require("./package.json").version,
12
- properties,
13
- extraPayload
14
- });
15
-
16
- if (shouldSend) {
17
- return sendTelemetry();
18
- }
19
- } catch (err) {
20
- // Ignore errors if telemetry is disabled.
21
- if (!shouldSend) {
22
- return;
23
- }
24
-
25
- throw err;
71
+ const data = loadJsonFileSync(path.join(process.cwd(), "package.json"));
72
+ return typeof data?.webiny?.installationId === "string" ? data.webiny.installationId : null;
73
+ } catch {
74
+ return null;
26
75
  }
27
76
  };
28
77
 
29
- const enable = () => {
78
+ export const enable = () => {
30
79
  globalConfig.set("telemetry", true);
31
80
  };
32
81
 
33
- const disable = () => {
82
+ export const disable = () => {
34
83
  globalConfig.set("telemetry", false);
35
84
  };
36
85
 
37
- const isEnabled = () => {
86
+ export const isEnabled = () => {
38
87
  const config = globalConfig.get();
39
88
 
40
89
  if (config.telemetry === false) {
@@ -44,5 +93,3 @@ const isEnabled = () => {
44
93
  // `tracking` is left here for backwards compatibility with previous versions of Webiny.
45
94
  return config.tracking !== false;
46
95
  };
47
-
48
- module.exports = { sendEvent, enable, disable, isEnabled };
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "@webiny/telemetry",
3
- "version": "0.0.0-unstable.b14eaecf38",
3
+ "version": "0.0.0-unstable.b6d7105cee",
4
+ "type": "module",
4
5
  "license": "MIT",
5
6
  "dependencies": {
6
- "@webiny/global-config": "0.0.0-unstable.b14eaecf38",
7
- "form-data": "3.0.0",
8
- "node-fetch": "2.6.1"
7
+ "@webiny/global-config": "0.0.0-unstable.b6d7105cee",
8
+ "@webiny/wts-client": "3.1.4",
9
+ "ci-info": "4.4.0",
10
+ "jsesc": "3.1.0",
11
+ "load-json-file": "7.0.1",
12
+ "strip-ansi": "7.2.0"
9
13
  },
10
14
  "publishConfig": {
11
- "access": "public",
12
- "directory": "."
13
- },
14
- "gitHead": "b14eaecf387253ed23cb96f8d31b7d16b5061936"
15
+ "access": "public"
16
+ }
15
17
  }
package/react.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function sendEvent(ev: string, properties?: Record<string, any>): Promise<any>;
2
+ export declare function getMachineId(): string | null;
package/react.js CHANGED
@@ -1,36 +1,122 @@
1
- const createSendEvent = require("./sendEvent");
1
+ import baseSendEvent from "./sendEvent.js";
2
+ import { WTS } from "@webiny/wts-client/web";
3
+
4
+ const STORAGE_MACHINE_ID = "wts_machine_id";
5
+ const STORAGE_PROJECT_ID = "wts_project_id";
6
+
7
+ let wtsInstance = null;
8
+ let projectId = null;
9
+ let distinctId = null;
2
10
 
3
- const setProperties = data => {
4
- return sendEvent("$identify", data);
5
- };
6
11
  /**
12
+ * Resolves the WTS client identity for the admin app.
7
13
  *
8
- * @param event {String}
9
- * @param data {Record<string, string>}
10
- * @return {Promise<T>}
14
+ * Priority for `distinct_id` (machine_id):
15
+ * 1. URL param `wts_did` on first load. Persisted to localStorage.
16
+ * 2. localStorage (subsequent loads).
17
+ * 3. `process.env.REACT_APP_WEBINY_TELEMETRY_USER_ID` (build-time fallback,
18
+ * set by `SetAdminAppEnvVarsBefore{Build,Watch}` from `~/.webiny/config`).
19
+ *
20
+ * Priority for `project_id` (installation_id):
21
+ * 1. URL param `iid` on first load. Persisted to localStorage.
22
+ * 2. localStorage.
23
+ * 3. `process.env.REACT_APP_WEBINY_INSTALLATION_ID` (build-time fallback,
24
+ * set from `<project>/package.json` β†’ `webiny.installationId`).
25
+ *
26
+ * Attached as a super-property on every admin event so PostHog funnels can
27
+ * group per-install.
11
28
  */
12
- const sendEvent = (event, data = {}) => {
13
- let properties = {};
14
- let extraPayload = {};
15
- if (event !== "$identify") {
16
- properties = data;
17
- } else {
18
- extraPayload = {
19
- $set: data
20
- };
29
+ const initWts = () => {
30
+ if (wtsInstance) {
31
+ return wtsInstance;
32
+ }
33
+
34
+ distinctId = process.env.REACT_APP_WEBINY_TELEMETRY_USER_ID;
35
+ projectId = process.env.REACT_APP_WEBINY_INSTALLATION_ID || null;
36
+
37
+ if (typeof window !== "undefined") {
38
+ const params = new URLSearchParams(window.location.search);
39
+
40
+ const fromUrl = params.get("wts_did");
41
+ if (fromUrl) {
42
+ distinctId = fromUrl;
43
+ try {
44
+ window.localStorage.setItem(STORAGE_MACHINE_ID, fromUrl);
45
+ } catch {
46
+ // localStorage unavailable; URL value is used for this session only.
47
+ }
48
+ } else {
49
+ try {
50
+ distinctId = window.localStorage.getItem(STORAGE_MACHINE_ID) || distinctId;
51
+ } catch {
52
+ // ignore
53
+ }
54
+ }
55
+
56
+ const iidFromUrl = params.get("iid");
57
+ if (iidFromUrl) {
58
+ projectId = iidFromUrl;
59
+ try {
60
+ window.localStorage.setItem(STORAGE_PROJECT_ID, iidFromUrl);
61
+ } catch {
62
+ // ignore
63
+ }
64
+ } else {
65
+ try {
66
+ projectId = window.localStorage.getItem(STORAGE_PROJECT_ID) || projectId;
67
+ } catch {
68
+ // env-var value (set above) is used as fallback.
69
+ }
70
+ }
21
71
  }
22
72
 
73
+ wtsInstance = new WTS({ source: "admin", distinctId });
74
+ return wtsInstance;
75
+ };
76
+
77
+ /**
78
+ * Returns the machine_id used by admin events, if known. Used by the
79
+ * install/finish CTA to construct the alias handoff URL.
80
+ */
81
+ export const getMachineId = () => {
82
+ initWts();
83
+ return distinctId || null;
84
+ };
85
+
86
+ export const sendEvent = async (event, properties = {}) => {
23
87
  const shouldSend = process.env.REACT_APP_WEBINY_TELEMETRY !== "false";
88
+ if (!shouldSend) {
89
+ return;
90
+ }
91
+
92
+ const wts = initWts();
24
93
 
25
- const sendTelemetry = createSendEvent({
94
+ const wcpProperties = {};
95
+ const [wcpOrgId, wcpProjectId] = getWcpOrgProjectId();
96
+ if (wcpOrgId && wcpProjectId) {
97
+ wcpProperties.wcpOrgId = wcpOrgId;
98
+ wcpProperties.wcpProjectId = wcpProjectId;
99
+ }
100
+
101
+ return baseSendEvent({
26
102
  event,
27
- properties,
28
- extraPayload,
29
- user: process.env.REACT_APP_USER_ID,
30
- version: process.env.REACT_APP_WEBINY_VERSION
103
+ properties: {
104
+ ...properties,
105
+ ...wcpProperties,
106
+ ...(projectId ? { project_id: projectId } : {}),
107
+ version: process.env.REACT_APP_WEBINY_VERSION,
108
+ ci: process.env.REACT_APP_IS_CI === "true",
109
+ newUser: process.env.REACT_APP_WEBINY_TELEMETRY_NEW_USER === "true"
110
+ },
111
+ wts
31
112
  });
32
-
33
- return shouldSend ? sendTelemetry() : Promise.resolve();
34
113
  };
35
114
 
36
- module.exports = { setProperties, sendEvent };
115
+ const getWcpOrgProjectId = () => {
116
+ // In React applications, project ID is stored in the `REACT_APP_WEBINY_PROJECT_ID` or `REACT_APP_WCP_PROJECT_ID` environment variable.
117
+ const id = process.env.REACT_APP_WEBINY_PROJECT_ID || process.env.REACT_APP_WCP_PROJECT_ID;
118
+ if (typeof id === "string") {
119
+ return id.split("/");
120
+ }
121
+ return [];
122
+ };
package/sendEvent.js CHANGED
@@ -1,56 +1,57 @@
1
- const FormData = require("form-data");
2
- const fetch = require("node-fetch");
3
-
4
- const API_KEY = "ZdDZgkeOt4Z_m-UWmqFsE1d6-kcCK3BH0ypYTUIFty4";
5
- const API_URL = "https://t.webiny.com";
1
+ import stripAnsi from "strip-ansi";
2
+ import jsesc from "jsesc";
6
3
 
7
4
  /**
8
5
  * The main `sendEvent` function.
9
6
  * NOTE: don't use this in your app directly. Instead, use the one from `cli.js` or `react.js` files accordingly.
7
+ *
8
+ * Identity is owned by the WTS instance β€” `cli.js` reads `~/.webiny/config`,
9
+ * `react.js` reads URL params / localStorage / env. This function only
10
+ * validates and sanitises the event payload before dispatching.
10
11
  */
11
- module.exports = ({ event, user, version, properties, extraPayload } = {}) => {
12
+ export default ({ event, properties, wts } = {}) => {
12
13
  if (!event) {
13
14
  throw new Error(`Cannot send event - missing "event" name.`);
14
15
  }
15
16
 
16
- if (!user) {
17
- throw new Error(`Cannot send event - missing "user" property.`);
17
+ if (!properties) {
18
+ throw new Error(`Cannot send event - missing "properties" object.`);
18
19
  }
19
20
 
20
- if (!version) {
21
+ if (!wts) {
22
+ throw new Error(`Cannot send event - missing "wts" instance.`);
23
+ }
24
+
25
+ if (!properties.version) {
21
26
  throw new Error(`Cannot send event - missing "version" property.`);
22
27
  }
23
28
 
24
- if (!properties) {
25
- properties = {};
29
+ const hasCiProp = "ci" in properties;
30
+ if (!hasCiProp) {
31
+ throw new Error(`Cannot send event - missing "ci" boolean property.`);
26
32
  }
27
33
 
28
- if (!extraPayload) {
29
- extraPayload = {};
34
+ const hasNewUserProp = "newUser" in properties;
35
+ if (!hasNewUserProp) {
36
+ throw new Error(`Cannot send event - missing "newUser" boolean property.`);
30
37
  }
31
38
 
32
- const payload = {
33
- ...extraPayload,
34
- event,
35
- properties: {
36
- ...properties,
37
- version
38
- },
39
- distinct_id: user,
40
- api_key: API_KEY,
41
- timestamp: new Date().toISOString()
39
+ const sanitizedProperties = {
40
+ ...properties,
41
+ newUser: properties.newUser === true ? "yes" : "no",
42
+ ci: properties.ci === true ? "yes" : "no"
42
43
  };
43
44
 
44
- const body = new FormData();
45
- body.append("data", Buffer.from(JSON.stringify(payload)).toString("base64"));
46
-
47
- // Return a function which will send the prepared body when invoked.
48
- return () => {
49
- return fetch(API_URL + "/capture/", {
50
- body,
51
- method: "POST"
52
- }).catch(() => {
53
- // Ignore errors
54
- });
55
- };
45
+ for (const key in sanitizedProperties) {
46
+ let sanitizedValue = sanitizedProperties[key];
47
+ if (typeof sanitizedValue === "string") {
48
+ sanitizedValue = sanitizedValue.trim();
49
+ sanitizedValue = stripAnsi(sanitizedValue);
50
+ sanitizedValue = jsesc(sanitizedValue);
51
+ }
52
+
53
+ sanitizedProperties[key] = sanitizedValue;
54
+ }
55
+
56
+ return wts.track(event, sanitizedProperties);
56
57
  };