watcha-feature/make-nextcloud-documents-integration-a-local-widget
parent
3ea0571c5f
commit
c5ea253181
@ -1,748 +0,0 @@ |
|||||||
// A clone of the type definitions from posthog-js, stripped of references to transitive
|
|
||||||
// dependencies which we don't actually use, so that we don't need to install them.
|
|
||||||
//
|
|
||||||
// Original file lives in node_modules/posthog/dist/module.d.ts
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/member-delimiter-style */ |
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */ |
|
||||||
/* eslint-disable camelcase */ |
|
||||||
|
|
||||||
// Type definitions for exported methods
|
|
||||||
|
|
||||||
declare class posthog { |
|
||||||
/** |
|
||||||
* This function initializes a new instance of the PostHog capturing object. |
|
||||||
* All new instances are added to the main posthog object as sub properties (such as |
|
||||||
* posthog.library_name) and also returned by this function. To define a |
|
||||||
* second instance on the page, you would call: |
|
||||||
* |
|
||||||
* posthog.init('new token', { your: 'config' }, 'library_name'); |
|
||||||
* |
|
||||||
* and use it like so: |
|
||||||
* |
|
||||||
* posthog.library_name.capture(...); |
|
||||||
* |
|
||||||
* @param {String} token Your PostHog API token |
|
||||||
* @param {Object} [config] A dictionary of config options to override. <a href="https://github.com/posthog/posthog-js/blob/6e0e873/src/posthog-core.js#L57-L91">See a list of default config options</a>. |
|
||||||
* @param {String} [name] The name for the new posthog instance that you want created |
|
||||||
*/ |
|
||||||
static init(token: string, config?: posthog.Config, name?: string): posthog |
|
||||||
|
|
||||||
/** |
|
||||||
* Clears super properties and generates a new random distinct_id for this instance. |
|
||||||
* Useful for clearing data when a user logs out. |
|
||||||
*/ |
|
||||||
static reset(reset_device_id?: boolean): void |
|
||||||
|
|
||||||
/** |
|
||||||
* Capture an event. This is the most important and |
|
||||||
* frequently used PostHog function. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* // capture an event named 'Registered'
|
|
||||||
* posthog.capture('Registered', {'Gender': 'Male', 'Age': 21}); |
|
||||||
* |
|
||||||
* // capture an event using navigator.sendBeacon
|
|
||||||
* posthog.capture('Left page', {'duration_seconds': 35}, {transport: 'sendBeacon'}); |
|
||||||
* |
|
||||||
* @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. |
|
||||||
* @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. |
|
||||||
* @param {Object} [options] Optional configuration for this capture request. |
|
||||||
* @param {String} [options.transport] Transport method for network request ('XHR' or 'sendBeacon'). |
|
||||||
*/ |
|
||||||
static capture( |
|
||||||
event_name: string, |
|
||||||
properties?: posthog.Properties, |
|
||||||
options?: { transport: 'XHR' | 'sendBeacon' } |
|
||||||
): posthog.CaptureResult |
|
||||||
|
|
||||||
/** |
|
||||||
* Capture a page view event, which is currently ignored by the server. |
|
||||||
* This function is called by default on page load unless the |
|
||||||
* capture_pageview configuration variable is false. |
|
||||||
* |
|
||||||
* @param {String} [page] The url of the page to record. If you don't include this, it defaults to the current url. |
|
||||||
* @api private |
|
||||||
*/ |
|
||||||
static capture_pageview(page?: string): void |
|
||||||
|
|
||||||
/** |
|
||||||
* Register a set of super properties, which are included with all |
|
||||||
* events. This will overwrite previous super property values. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* // register 'Gender' as a super property
|
|
||||||
* posthog.register({'Gender': 'Female'}); |
|
||||||
* |
|
||||||
* // register several super properties when a user signs up
|
|
||||||
* posthog.register({ |
|
||||||
* 'Email': 'jdoe@example.com', |
|
||||||
* 'Account Type': 'Free' |
|
||||||
* }); |
|
||||||
* |
|
||||||
* @param {Object} properties An associative array of properties to store about the user |
|
||||||
* @param {Number} [days] How many days since the user's last visit to store the super properties |
|
||||||
*/ |
|
||||||
static register(properties: posthog.Properties, days?: number): void |
|
||||||
|
|
||||||
/** |
|
||||||
* Register a set of super properties only once. This will not |
|
||||||
* overwrite previous super property values, unlike register(). |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* // register a super property for the first time only
|
|
||||||
* posthog.register_once({ |
|
||||||
* 'First Login Date': new Date().toISOString() |
|
||||||
* }); |
|
||||||
* |
|
||||||
* ### Notes: |
|
||||||
* |
|
||||||
* If default_value is specified, current super properties |
|
||||||
* with that value will be overwritten. |
|
||||||
* |
|
||||||
* @param {Object} properties An associative array of properties to store about the user |
|
||||||
* @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None' |
|
||||||
* @param {Number} [days] How many days since the users last visit to store the super properties |
|
||||||
*/ |
|
||||||
static register_once(properties: posthog.Properties, default_value?: posthog.Property, days?: number): void |
|
||||||
|
|
||||||
/** |
|
||||||
* Delete a super property stored with the current user. |
|
||||||
* |
|
||||||
* @param {String} property The name of the super property to remove |
|
||||||
*/ |
|
||||||
static unregister(property: string): void |
|
||||||
|
|
||||||
/** |
|
||||||
* Identify a user with a unique ID instead of a PostHog |
|
||||||
* randomly generated distinct_id. If the method is never called, |
|
||||||
* then unique visitors will be identified by a UUID generated |
|
||||||
* the first time they visit the site. |
|
||||||
* |
|
||||||
* If user properties are passed, they are also sent to posthog. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* posthog.identify('[user unique id]') |
|
||||||
* posthog.identify('[user unique id]', { email: 'john@example.com' }) |
|
||||||
* posthog.identify('[user unique id]', {}, { referral_code: '12345' }) |
|
||||||
* |
|
||||||
* ### Notes: |
|
||||||
* |
|
||||||
* You can call this function to overwrite a previously set |
|
||||||
* unique ID for the current user. PostHog cannot translate |
|
||||||
* between IDs at this time, so when you change a user's ID |
|
||||||
* they will appear to be a new user. |
|
||||||
* |
|
||||||
* When used alone, posthog.identify will change the user's |
|
||||||
* distinct_id to the unique ID provided. When used in tandem |
|
||||||
* with posthog.alias, it will allow you to identify based on |
|
||||||
* unique ID and map that back to the original, anonymous |
|
||||||
* distinct_id given to the user upon her first arrival to your |
|
||||||
* site (thus connecting anonymous pre-signup activity to |
|
||||||
* post-signup activity). Though the two work together, do not |
|
||||||
* call identify() at the same time as alias(). Calling the two |
|
||||||
* at the same time can cause a race condition, so it is best |
|
||||||
* practice to call identify on the original, anonymous ID |
|
||||||
* right after you've aliased it. |
|
||||||
* |
|
||||||
* @param {String} [unique_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used. |
|
||||||
* @param {Object} [userProperties] Optional: An associative array of properties to store about the user |
|
||||||
* @param {Object} [userPropertiesToSetOnce] Optional: An associative array of properties to store about the user. If property is previously set, this does not override that value. |
|
||||||
*/ |
|
||||||
static identify( |
|
||||||
unique_id?: string, |
|
||||||
userPropertiesToSet?: posthog.Properties, |
|
||||||
userPropertiesToSetOnce?: posthog.Properties |
|
||||||
): void |
|
||||||
|
|
||||||
/** |
|
||||||
* Create an alias, which PostHog will use to link two distinct_ids going forward (not retroactively). |
|
||||||
* Multiple aliases can map to the same original ID, but not vice-versa. Aliases can also be chained - the |
|
||||||
* following is a valid scenario: |
|
||||||
* |
|
||||||
* posthog.alias('new_id', 'existing_id'); |
|
||||||
* ... |
|
||||||
* posthog.alias('newer_id', 'new_id'); |
|
||||||
* |
|
||||||
* If the original ID is not passed in, we will use the current distinct_id - probably the auto-generated GUID. |
|
||||||
* |
|
||||||
* ### Notes: |
|
||||||
* |
|
||||||
* The best practice is to call alias() when a unique ID is first created for a user |
|
||||||
* (e.g., when a user first registers for an account and provides an email address). |
|
||||||
* alias() should never be called more than once for a given user, except to |
|
||||||
* chain a newer ID to a previously new ID, as described above. |
|
||||||
* |
|
||||||
* @param {String} alias A unique identifier that you want to use for this user in the future. |
|
||||||
* @param {String} [original] The current identifier being used for this user. |
|
||||||
*/ |
|
||||||
static alias(alias: string, original?: string): posthog.CaptureResult | number |
|
||||||
|
|
||||||
/** |
|
||||||
* Update the configuration of a posthog library instance. |
|
||||||
* |
|
||||||
* The default config is: |
|
||||||
* |
|
||||||
* { |
|
||||||
* // HTTP method for capturing requests
|
|
||||||
* api_method: 'POST' |
|
||||||
* |
|
||||||
* // transport for sending requests ('XHR' or 'sendBeacon')
|
|
||||||
* // NB: sendBeacon should only be used for scenarios such as
|
|
||||||
* // page unload where a "best-effort" attempt to send is
|
|
||||||
* // acceptable; the sendBeacon API does not support callbacks
|
|
||||||
* // or any way to know the result of the request. PostHog
|
|
||||||
* // capturing via sendBeacon will not support any event-
|
|
||||||
* // batching or retry mechanisms.
|
|
||||||
* api_transport: 'XHR' |
|
||||||
* |
|
||||||
* // Automatically capture clicks, form submissions and change events
|
|
||||||
* autocapture: true |
|
||||||
* |
|
||||||
* // Capture rage clicks (beta) - useful for session recording
|
|
||||||
* rageclick: false |
|
||||||
* |
|
||||||
* // super properties cookie expiration (in days)
|
|
||||||
* cookie_expiration: 365 |
|
||||||
* |
|
||||||
* // super properties span subdomains
|
|
||||||
* cross_subdomain_cookie: true |
|
||||||
* |
|
||||||
* // debug mode
|
|
||||||
* debug: false |
|
||||||
* |
|
||||||
* // if this is true, the posthog cookie or localStorage entry
|
|
||||||
* // will be deleted, and no user persistence will take place
|
|
||||||
* disable_persistence: false |
|
||||||
* |
|
||||||
* // if this is true, PostHog will automatically determine
|
|
||||||
* // City, Region and Country data using the IP address of
|
|
||||||
* //the client
|
|
||||||
* ip: true |
|
||||||
* |
|
||||||
* // opt users out of capturing by this PostHog instance by default
|
|
||||||
* opt_out_capturing_by_default: false |
|
||||||
* |
|
||||||
* // opt users out of browser data storage by this PostHog instance by default
|
|
||||||
* opt_out_persistence_by_default: false |
|
||||||
* |
|
||||||
* // persistence mechanism used by opt-in/opt-out methods - cookie
|
|
||||||
* // or localStorage - falls back to cookie if localStorage is unavailable
|
|
||||||
* opt_out_capturing_persistence_type: 'localStorage' |
|
||||||
* |
|
||||||
* // customize the name of cookie/localStorage set by opt-in/opt-out methods
|
|
||||||
* opt_out_capturing_cookie_prefix: null |
|
||||||
* |
|
||||||
* // type of persistent store for super properties (cookie/
|
|
||||||
* // localStorage) if set to 'localStorage', any existing
|
|
||||||
* // posthog cookie value with the same persistence_name
|
|
||||||
* // will be transferred to localStorage and deleted
|
|
||||||
* persistence: 'cookie' |
|
||||||
* |
|
||||||
* // name for super properties persistent store
|
|
||||||
* persistence_name: '' |
|
||||||
* |
|
||||||
* // names of properties/superproperties which should never
|
|
||||||
* // be sent with capture() calls
|
|
||||||
* property_blacklist: [] |
|
||||||
* |
|
||||||
* // if this is true, posthog cookies will be marked as
|
|
||||||
* // secure, meaning they will only be transmitted over https
|
|
||||||
* secure_cookie: false |
|
||||||
* |
|
||||||
* // should we capture a page view on page load
|
|
||||||
* capture_pageview: true |
|
||||||
* |
|
||||||
* // if you set upgrade to be true, the library will check for
|
|
||||||
* // a cookie from our old js library and import super
|
|
||||||
* // properties from it, then the old cookie is deleted
|
|
||||||
* // The upgrade config option only works in the initialization,
|
|
||||||
* // so make sure you set it when you create the library.
|
|
||||||
* upgrade: false |
|
||||||
* |
|
||||||
* // extra HTTP request headers to set for each API request, in
|
|
||||||
* // the format {'Header-Name': value}
|
|
||||||
* xhr_headers: {} |
|
||||||
* |
|
||||||
* // protocol for fetching in-app message resources, e.g.
|
|
||||||
* // 'https://' or 'http://'; defaults to '//' (which defers to the
|
|
||||||
* // current page's protocol)
|
|
||||||
* inapp_protocol: '//' |
|
||||||
* |
|
||||||
* // whether to open in-app message link in new tab/window
|
|
||||||
* inapp_link_new_window: false |
|
||||||
* |
|
||||||
* // a set of rrweb config options that PostHog users can configure
|
|
||||||
* // see https://github.com/rrweb-io/rrweb/blob/master/guide.md
|
|
||||||
* session_recording: { |
|
||||||
* blockClass: 'ph-no-capture', |
|
||||||
* blockSelector: null, |
|
||||||
* ignoreClass: 'ph-ignore-input', |
|
||||||
* maskAllInputs: false, |
|
||||||
* maskInputOptions: {}, |
|
||||||
* maskInputFn: null, |
|
||||||
* slimDOMOptions: {}, |
|
||||||
* collectFonts: false |
|
||||||
* } |
|
||||||
* |
|
||||||
* // prevent autocapture from capturing any attribute names on elements
|
|
||||||
* mask_all_element_attributes: false |
|
||||||
* |
|
||||||
* // prevent autocapture from capturing textContent on all elements
|
|
||||||
* mask_all_text: false |
|
||||||
* |
|
||||||
* // will disable requests to the /decide endpoint (please review documentation for details)
|
|
||||||
* // autocapture, feature flags, compression and session recording will be disabled when set to `true`
|
|
||||||
* advanced_disable_decide: false |
|
||||||
* |
|
||||||
* } |
|
||||||
* |
|
||||||
* |
|
||||||
* @param {Object} config A dictionary of new configuration values to update |
|
||||||
*/ |
|
||||||
static set_config(config: posthog.Config): void |
|
||||||
|
|
||||||
/** |
|
||||||
* returns the current config object for the library. |
|
||||||
*/ |
|
||||||
static get_config<T extends keyof posthog.Config>(prop_name: T): posthog.Config[T] |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the value of the super property named property_name. If no such |
|
||||||
* property is set, get_property() will return the undefined value. |
|
||||||
* |
|
||||||
* ### Notes: |
|
||||||
* |
|
||||||
* get_property() can only be called after the PostHog library has finished loading. |
|
||||||
* init() has a loaded function available to handle this automatically. For example: |
|
||||||
* |
|
||||||
* // grab value for 'user_id' after the posthog library has loaded
|
|
||||||
* posthog.init('YOUR PROJECT TOKEN', { |
|
||||||
* loaded: function(posthog) { |
|
||||||
* user_id = posthog.get_property('user_id'); |
|
||||||
* } |
|
||||||
* }); |
|
||||||
* |
|
||||||
* @param {String} property_name The name of the super property you want to retrieve |
|
||||||
*/ |
|
||||||
static get_property(property_name: string): posthog.Property | undefined |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the current distinct id of the user. This is either the id automatically |
|
||||||
* generated by the library or the id that has been passed by a call to identify(). |
|
||||||
* |
|
||||||
* ### Notes: |
|
||||||
* |
|
||||||
* get_distinct_id() can only be called after the PostHog library has finished loading. |
|
||||||
* init() has a loaded function available to handle this automatically. For example: |
|
||||||
* |
|
||||||
* // set distinct_id after the posthog library has loaded
|
|
||||||
* posthog.init('YOUR PROJECT TOKEN', { |
|
||||||
* loaded: function(posthog) { |
|
||||||
* distinct_id = posthog.get_distinct_id(); |
|
||||||
* } |
|
||||||
* }); |
|
||||||
*/ |
|
||||||
static get_distinct_id(): string |
|
||||||
|
|
||||||
/** |
|
||||||
* Opt the user out of data capturing and cookies/localstorage for this PostHog instance |
|
||||||
* |
|
||||||
* ### Usage |
|
||||||
* |
|
||||||
* // opt user out
|
|
||||||
* posthog.opt_out_capturing(); |
|
||||||
* |
|
||||||
* // opt user out with different cookie configuration from PostHog instance
|
|
||||||
* posthog.opt_out_capturing({ |
|
||||||
* cookie_expiration: 30, |
|
||||||
* secure_cookie: true |
|
||||||
* }); |
|
||||||
* |
|
||||||
* @param {Object} [options] A dictionary of config options to override |
|
||||||
* @param {boolean} [options.clear_persistence=true] If true, will delete all data stored by the sdk in persistence |
|
||||||
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable |
|
||||||
* @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name |
|
||||||
* @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) |
|
||||||
* @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) |
|
||||||
* @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) |
|
||||||
*/ |
|
||||||
static opt_out_capturing(options?: posthog.OptInOutCapturingOptions): void |
|
||||||
|
|
||||||
/** |
|
||||||
* Opt the user in to data capturing and cookies/localstorage for this PostHog instance |
|
||||||
* |
|
||||||
* ### Usage |
|
||||||
* |
|
||||||
* // opt user in
|
|
||||||
* posthog.opt_in_capturing(); |
|
||||||
* |
|
||||||
* // opt user in with specific event name, properties, cookie configuration
|
|
||||||
* posthog.opt_in_capturing({ |
|
||||||
* capture_event_name: 'User opted in', |
|
||||||
* capture_event_properties: { |
|
||||||
* 'Email': 'jdoe@example.com' |
|
||||||
* }, |
|
||||||
* cookie_expiration: 30, |
|
||||||
* secure_cookie: true |
|
||||||
* }); |
|
||||||
* |
|
||||||
* @param {Object} [options] A dictionary of config options to override |
|
||||||
* @param {function} [options.capture] Function used for capturing a PostHog event to record the opt-in action (default is this PostHog instance's capture method) |
|
||||||
* @param {string} [options.capture_event_name=$opt_in] Event name to be used for capturing the opt-in action |
|
||||||
* @param {Object} [options.capture_properties] Set of properties to be captured along with the opt-in action |
|
||||||
* @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence |
|
||||||
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable |
|
||||||
* @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name |
|
||||||
* @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) |
|
||||||
* @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) |
|
||||||
* @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) |
|
||||||
*/ |
|
||||||
static opt_in_capturing(options?: posthog.OptInOutCapturingOptions): void |
|
||||||
|
|
||||||
/** |
|
||||||
* Check whether the user has opted out of data capturing and cookies/localstorage for this PostHog instance |
|
||||||
* |
|
||||||
* ### Usage |
|
||||||
* |
|
||||||
* const has_opted_out = posthog.has_opted_out_capturing(); |
|
||||||
* // use has_opted_out value
|
|
||||||
* |
|
||||||
* @param {Object} [options] A dictionary of config options to override |
|
||||||
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable |
|
||||||
* @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name |
|
||||||
* @returns {boolean} current opt-out status |
|
||||||
*/ |
|
||||||
static has_opted_out_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean |
|
||||||
|
|
||||||
/** |
|
||||||
* Check whether the user has opted in to data capturing and cookies/localstorage for this PostHog instance |
|
||||||
* |
|
||||||
* ### Usage |
|
||||||
* |
|
||||||
* const has_opted_in = posthog.has_opted_in_capturing(); |
|
||||||
* // use has_opted_in value
|
|
||||||
* |
|
||||||
* @param {Object} [options] A dictionary of config options to override |
|
||||||
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable |
|
||||||
* @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name |
|
||||||
* @returns {boolean} current opt-in status |
|
||||||
*/ |
|
||||||
static has_opted_in_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean |
|
||||||
|
|
||||||
/** |
|
||||||
* Clear the user's opt in/out status of data capturing and cookies/localstorage for this PostHog instance |
|
||||||
* |
|
||||||
* ### Usage |
|
||||||
* |
|
||||||
* // clear user's opt-in/out status
|
|
||||||
* posthog.clear_opt_in_out_capturing(); |
|
||||||
* |
|
||||||
* // clear user's opt-in/out status with specific cookie configuration - should match
|
|
||||||
* // configuration used when opt_in_capturing/opt_out_capturing methods were called.
|
|
||||||
* posthog.clear_opt_in_out_capturing({ |
|
||||||
* cookie_expiration: 30, |
|
||||||
* secure_cookie: true |
|
||||||
* }); |
|
||||||
* |
|
||||||
* @param {Object} [options] A dictionary of config options to override |
|
||||||
* @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence |
|
||||||
* @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable |
|
||||||
* @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name |
|
||||||
* @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) |
|
||||||
* @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) |
|
||||||
* @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) |
|
||||||
*/ |
|
||||||
static clear_opt_in_out_capturing(options?: posthog.ClearOptInOutCapturingOptions): void |
|
||||||
|
|
||||||
/* |
|
||||||
* See if feature flag is enabled for user. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* if(posthog.isFeatureEnabled('beta-feature')) { // do something }
|
|
||||||
* |
|
||||||
* @param {Object|String} prop Key of the feature flag. |
|
||||||
* @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. |
|
||||||
*/ |
|
||||||
static isFeatureEnabled(key: string, options?: posthog.isFeatureEnabledOptions): boolean |
|
||||||
|
|
||||||
/* |
|
||||||
* See if feature flags are available. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* posthog.onFeatureFlags(function(featureFlags) { // do something })
|
|
||||||
* |
|
||||||
* @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. |
|
||||||
*/ |
|
||||||
static onFeatureFlags(callback: (flags: string[]) => void): false | undefined |
|
||||||
|
|
||||||
/* |
|
||||||
* Reload all feature flags for the user. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* posthog.reloadFeatureFlags() |
|
||||||
*/ |
|
||||||
static reloadFeatureFlags(): void |
|
||||||
|
|
||||||
static toString(): string |
|
||||||
|
|
||||||
/* Will log all capture requests to the Javascript console, including event properties for easy debugging */ |
|
||||||
static debug(): void |
|
||||||
|
|
||||||
/* |
|
||||||
* Starts session recording and updates disable_session_recording to false. |
|
||||||
* Used for manual session recording management. By default, session recording is enabled and |
|
||||||
* starts automatically. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* posthog.startSessionRecording() |
|
||||||
*/ |
|
||||||
static startSessionRecording(): void |
|
||||||
|
|
||||||
/* |
|
||||||
* Stops session recording and updates disable_session_recording to true. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* posthog.stopSessionRecording() |
|
||||||
*/ |
|
||||||
static stopSessionRecording(): void |
|
||||||
|
|
||||||
/* |
|
||||||
* Check if session recording is currently running. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* const isSessionRecordingOn = posthog.sessionRecordingStarted() |
|
||||||
*/ |
|
||||||
static sessionRecordingStarted(): boolean |
|
||||||
} |
|
||||||
|
|
||||||
declare namespace posthog { |
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
|
||||||
type Property = any; |
|
||||||
type Properties = Record<string, Property>; |
|
||||||
type CaptureResult = { event: string; properties: Properties } | undefined; |
|
||||||
type CaptureCallback = (response: any, data: any) => void; |
|
||||||
/* eslint-enable @typescript-eslint/no-explicit-any */ |
|
||||||
|
|
||||||
interface Config { |
|
||||||
api_host?: string |
|
||||||
api_method?: string |
|
||||||
api_transport?: string |
|
||||||
autocapture?: boolean |
|
||||||
rageclick?: boolean |
|
||||||
cdn?: string |
|
||||||
cross_subdomain_cookie?: boolean |
|
||||||
persistence?: 'localStorage' | 'cookie' | 'memory' |
|
||||||
persistence_name?: string |
|
||||||
cookie_name?: string |
|
||||||
loaded?: (posthog_instance: typeof posthog) => void |
|
||||||
store_google?: boolean |
|
||||||
save_referrer?: boolean |
|
||||||
test?: boolean |
|
||||||
verbose?: boolean |
|
||||||
img?: boolean |
|
||||||
capture_pageview?: boolean |
|
||||||
debug?: boolean |
|
||||||
cookie_expiration?: number |
|
||||||
upgrade?: boolean |
|
||||||
disable_session_recording?: boolean |
|
||||||
disable_persistence?: boolean |
|
||||||
disable_cookie?: boolean |
|
||||||
secure_cookie?: boolean |
|
||||||
ip?: boolean |
|
||||||
opt_out_capturing_by_default?: boolean |
|
||||||
opt_out_persistence_by_default?: boolean |
|
||||||
opt_out_capturing_persistence_type?: 'localStorage' | 'cookie' |
|
||||||
opt_out_capturing_cookie_prefix?: string | null |
|
||||||
respect_dnt?: boolean |
|
||||||
property_blacklist?: string[] |
|
||||||
xhr_headers?: { [header_name: string]: string } |
|
||||||
inapp_protocol?: string |
|
||||||
inapp_link_new_window?: boolean |
|
||||||
request_batching?: boolean |
|
||||||
sanitize_properties?: (properties: posthog.Properties, event_name: string) => posthog.Properties |
|
||||||
properties_string_max_length?: number |
|
||||||
mask_all_element_attributes?: boolean |
|
||||||
mask_all_text?: boolean |
|
||||||
advanced_disable_decide?: boolean |
|
||||||
} |
|
||||||
|
|
||||||
interface OptInOutCapturingOptions { |
|
||||||
clear_persistence: boolean |
|
||||||
persistence_type: string |
|
||||||
cookie_prefix: string |
|
||||||
cookie_expiration: number |
|
||||||
cross_subdomain_cookie: boolean |
|
||||||
secure_cookie: boolean |
|
||||||
} |
|
||||||
|
|
||||||
interface HasOptedInOutCapturingOptions { |
|
||||||
persistence_type: string |
|
||||||
cookie_prefix: string |
|
||||||
} |
|
||||||
|
|
||||||
interface ClearOptInOutCapturingOptions { |
|
||||||
enable_persistence: boolean |
|
||||||
persistence_type: string |
|
||||||
cookie_prefix: string |
|
||||||
cookie_expiration: number |
|
||||||
cross_subdomain_cookie: boolean |
|
||||||
secure_cookie: boolean |
|
||||||
} |
|
||||||
|
|
||||||
interface isFeatureEnabledOptions { |
|
||||||
send_event: boolean |
|
||||||
} |
|
||||||
|
|
||||||
export class persistence { |
|
||||||
static properties(): posthog.Properties |
|
||||||
|
|
||||||
static load(): void |
|
||||||
|
|
||||||
static save(): void |
|
||||||
|
|
||||||
static remove(): void |
|
||||||
|
|
||||||
static clear(): void |
|
||||||
|
|
||||||
/** |
|
||||||
* @param {Object} props |
|
||||||
* @param {*=} default_value |
|
||||||
* @param {number=} days |
|
||||||
*/ |
|
||||||
static register_once(props: Properties, default_value?: Property, days?: number): boolean |
|
||||||
|
|
||||||
/** |
|
||||||
* @param {Object} props |
|
||||||
* @param {number=} days |
|
||||||
*/ |
|
||||||
static register(props: posthog.Properties, days?: number): boolean |
|
||||||
|
|
||||||
static unregister(prop: string): void |
|
||||||
|
|
||||||
static update_campaign_params(): void |
|
||||||
|
|
||||||
static update_search_keyword(referrer: string): void |
|
||||||
|
|
||||||
static update_referrer_info(referrer: string): void |
|
||||||
|
|
||||||
static get_referrer_info(): posthog.Properties |
|
||||||
|
|
||||||
static safe_merge(props: posthog.Properties): posthog.Properties |
|
||||||
|
|
||||||
static update_config(config: posthog.Config): void |
|
||||||
|
|
||||||
static set_disabled(disabled: boolean): void |
|
||||||
|
|
||||||
static set_cross_subdomain(cross_subdomain: boolean): void |
|
||||||
|
|
||||||
static get_cross_subdomain(): boolean |
|
||||||
|
|
||||||
static set_secure(secure: boolean): void |
|
||||||
|
|
||||||
static set_event_timer(event_name: string, timestamp: Date): void |
|
||||||
|
|
||||||
static remove_event_timer(event_name: string): Date | undefined |
|
||||||
} |
|
||||||
|
|
||||||
export class people { |
|
||||||
/* |
|
||||||
* Set properties on a user record. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* posthog.people.set('gender', 'm'); |
|
||||||
* |
|
||||||
* // or set multiple properties at once
|
|
||||||
* posthog.people.set({ |
|
||||||
* 'Company': 'Acme', |
|
||||||
* 'Plan': 'Premium', |
|
||||||
* 'Upgrade date': new Date() |
|
||||||
* }); |
|
||||||
* // properties can be strings, integers, dates, or lists
|
|
||||||
* |
|
||||||
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. |
|
||||||
* @param {*} [to] A value to set on the given property name |
|
||||||
* @param {Function} [callback] If provided, the callback will be called after capturing the event. |
|
||||||
*/ |
|
||||||
static set( |
|
||||||
prop: posthog.Properties | string, |
|
||||||
to?: posthog.Property, |
|
||||||
callback?: posthog.CaptureCallback |
|
||||||
): posthog.Properties |
|
||||||
|
|
||||||
/* |
|
||||||
* Set properties on a user record, only if they do not yet exist. |
|
||||||
* This will not overwrite previous people property values, unlike |
|
||||||
* people.set(). |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* posthog.people.set_once('First Login Date', new Date()); |
|
||||||
* |
|
||||||
* // or set multiple properties at once
|
|
||||||
* posthog.people.set_once({ |
|
||||||
* 'First Login Date': new Date(), |
|
||||||
* 'Starting Plan': 'Premium' |
|
||||||
* }); |
|
||||||
* |
|
||||||
* // properties can be strings, integers or dates
|
|
||||||
* |
|
||||||
* @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. |
|
||||||
* @param {*} [to] A value to set on the given property name |
|
||||||
* @param {Function} [callback] If provided, the callback will be called after capturing the event. |
|
||||||
*/ |
|
||||||
static set_once( |
|
||||||
prop: posthog.Properties | string, |
|
||||||
to?: posthog.Property, |
|
||||||
callback?: posthog.CaptureCallback |
|
||||||
): posthog.Properties |
|
||||||
|
|
||||||
static toString(): string |
|
||||||
} |
|
||||||
|
|
||||||
export class featureFlags { |
|
||||||
static getFlags(): string[] |
|
||||||
|
|
||||||
static reloadFeatureFlags(): void |
|
||||||
|
|
||||||
/* |
|
||||||
* See if feature flag is enabled for user. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* if(posthog.isFeatureEnabled('beta-feature')) { // do something }
|
|
||||||
* |
|
||||||
* @param {Object|String} prop Key of the feature flag. |
|
||||||
* @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. |
|
||||||
*/ |
|
||||||
static isFeatureEnabled(key: string, options?: { send_event?: boolean }): boolean |
|
||||||
|
|
||||||
/* |
|
||||||
* See if feature flags are available. |
|
||||||
* |
|
||||||
* ### Usage: |
|
||||||
* |
|
||||||
* posthog.onFeatureFlags(function(featureFlags) { // do something })
|
|
||||||
* |
|
||||||
* @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. |
|
||||||
*/ |
|
||||||
static onFeatureFlags(callback: (flags: string[]) => void): false | undefined |
|
||||||
} |
|
||||||
|
|
||||||
export class feature_flags extends featureFlags {} |
|
||||||
} |
|
||||||
|
|
||||||
export type PostHog = typeof posthog; |
|
||||||
|
|
||||||
export default posthog; |
|
||||||
@ -1,355 +0,0 @@ |
|||||||
/* |
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C. |
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
you may not use this file except in compliance with the License. |
|
||||||
You may obtain a copy of the License at |
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software |
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
See the License for the specific language governing permissions and |
|
||||||
limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
import posthog, { PostHog } from 'posthog-js'; |
|
||||||
import PlatformPeg from './PlatformPeg'; |
|
||||||
import SdkConfig from './SdkConfig'; |
|
||||||
import SettingsStore from './settings/SettingsStore'; |
|
||||||
|
|
||||||
/* Posthog analytics tracking. |
|
||||||
* |
|
||||||
* Anonymity behaviour is as follows: |
|
||||||
* |
|
||||||
* - If Posthog isn't configured in `config.json`, events are not sent. |
|
||||||
* - If [Do Not Track](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/doNotTrack) is
|
|
||||||
* enabled, events are not sent (this detection is built into posthog and turned on via the |
|
||||||
* `respect_dnt` flag being passed to `posthog.init`). |
|
||||||
* - If the `feature_pseudonymous_analytics_opt_in` labs flag is `true`, track pseudonomously, i.e. |
|
||||||
* hash all matrix identifiers in tracking events (user IDs, room IDs etc) using SHA-256. |
|
||||||
* - Otherwise, if the existing `analyticsOptIn` flag is `true`, track anonymously, i.e. |
|
||||||
* redact all matrix identifiers in tracking events. |
|
||||||
* - If both flags are false or not set, events are not sent. |
|
||||||
*/ |
|
||||||
|
|
||||||
interface IEvent { |
|
||||||
// The event name that will be used by PostHog. Event names should use snake_case.
|
|
||||||
eventName: string; |
|
||||||
|
|
||||||
// The properties of the event that will be stored in PostHog. This is just a placeholder,
|
|
||||||
// extending interfaces must override this with a concrete definition to do type validation.
|
|
||||||
properties: {}; |
|
||||||
} |
|
||||||
|
|
||||||
export enum Anonymity { |
|
||||||
Disabled, |
|
||||||
Anonymous, |
|
||||||
Pseudonymous |
|
||||||
} |
|
||||||
|
|
||||||
// If an event extends IPseudonymousEvent, the event contains pseudonymous data
|
|
||||||
// that won't be sent unless the user has explicitly consented to pseudonymous tracking.
|
|
||||||
// For example, it might contain hashed user IDs or room IDs.
|
|
||||||
// Such events will be automatically dropped if PosthogAnalytics.anonymity isn't set to Pseudonymous.
|
|
||||||
export interface IPseudonymousEvent extends IEvent {} |
|
||||||
|
|
||||||
// If an event extends IAnonymousEvent, the event strictly contains *only* anonymous data;
|
|
||||||
// i.e. no identifiers that can be associated with the user.
|
|
||||||
export interface IAnonymousEvent extends IEvent {} |
|
||||||
|
|
||||||
export interface IRoomEvent extends IPseudonymousEvent { |
|
||||||
hashedRoomId: string; |
|
||||||
} |
|
||||||
|
|
||||||
interface IPageView extends IAnonymousEvent { |
|
||||||
eventName: "$pageview"; |
|
||||||
properties: { |
|
||||||
durationMs?: number; |
|
||||||
screen?: string; |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
const hashHex = async (input: string): Promise<string> => { |
|
||||||
const buf = new TextEncoder().encode(input); |
|
||||||
const digestBuf = await window.crypto.subtle.digest("sha-256", buf); |
|
||||||
return [...new Uint8Array(digestBuf)].map((b: number) => b.toString(16).padStart(2, "0")).join(""); |
|
||||||
}; |
|
||||||
|
|
||||||
const whitelistedScreens = new Set([ |
|
||||||
"register", "login", "forgot_password", "soft_logout", "new", "settings", "welcome", "home", "start", "directory", |
|
||||||
"start_sso", "start_cas", "groups", "complete_security", "post_registration", "room", "user", "group", |
|
||||||
]); |
|
||||||
|
|
||||||
export async function getRedactedCurrentLocation( |
|
||||||
origin: string, |
|
||||||
hash: string, |
|
||||||
pathname: string, |
|
||||||
anonymity: Anonymity, |
|
||||||
): Promise<string> { |
|
||||||
// Redact PII from the current location.
|
|
||||||
// If anonymous is true, redact entirely, if false, substitute it with a hash.
|
|
||||||
// For known screens, assumes a URL structure of /<screen name>/might/be/pii
|
|
||||||
if (origin.startsWith('file://')) { |
|
||||||
pathname = "/<redacted_file_scheme_url>/"; |
|
||||||
} |
|
||||||
|
|
||||||
let hashStr; |
|
||||||
if (hash == "") { |
|
||||||
hashStr = ""; |
|
||||||
} else { |
|
||||||
let [beforeFirstSlash, screen, ...parts] = hash.split("/"); |
|
||||||
|
|
||||||
if (!whitelistedScreens.has(screen)) { |
|
||||||
screen = "<redacted_screen_name>"; |
|
||||||
} |
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i++) { |
|
||||||
parts[i] = anonymity === Anonymity.Anonymous ? `<redacted>` : await hashHex(parts[i]); |
|
||||||
} |
|
||||||
|
|
||||||
hashStr = `${beforeFirstSlash}/${screen}/${parts.join("/")}`; |
|
||||||
} |
|
||||||
return origin + pathname + hashStr; |
|
||||||
} |
|
||||||
|
|
||||||
interface PlatformProperties { |
|
||||||
appVersion: string; |
|
||||||
appPlatform: string; |
|
||||||
} |
|
||||||
|
|
||||||
export class PosthogAnalytics { |
|
||||||
/* Wrapper for Posthog analytics. |
|
||||||
* 3 modes of anonymity are supported, governed by this.anonymity |
|
||||||
* - Anonymity.Disabled means *no data* is passed to posthog |
|
||||||
* - Anonymity.Anonymous means all identifers will be redacted before being passed to posthog |
|
||||||
* - Anonymity.Pseudonymous means all identifiers will be hashed via SHA-256 before being passed |
|
||||||
* to Posthog |
|
||||||
* |
|
||||||
* To update anonymity, call updateAnonymityFromSettings() or you can set it directly via setAnonymity(). |
|
||||||
* |
|
||||||
* To pass an event to Posthog: |
|
||||||
* |
|
||||||
* 1. Declare a type for the event, extending IAnonymousEvent, IPseudonymousEvent or IRoomEvent. |
|
||||||
* 2. Call the appropriate track*() method. Pseudonymous events will be dropped when anonymity is |
|
||||||
* Anonymous or Disabled; Anonymous events will be dropped when anonymity is Disabled. |
|
||||||
*/ |
|
||||||
|
|
||||||
private anonymity = Anonymity.Disabled; |
|
||||||
// set true during the constructor if posthog config is present, otherwise false
|
|
||||||
private enabled = false; |
|
||||||
private static _instance = null; |
|
||||||
private platformSuperProperties = {}; |
|
||||||
|
|
||||||
public static get instance(): PosthogAnalytics { |
|
||||||
if (!this._instance) { |
|
||||||
this._instance = new PosthogAnalytics(posthog); |
|
||||||
} |
|
||||||
return this._instance; |
|
||||||
} |
|
||||||
|
|
||||||
constructor(private readonly posthog: PostHog) { |
|
||||||
const posthogConfig = SdkConfig.get()["posthog"]; |
|
||||||
if (posthogConfig) { |
|
||||||
this.posthog.init(posthogConfig.projectApiKey, { |
|
||||||
api_host: posthogConfig.apiHost, |
|
||||||
autocapture: false, |
|
||||||
mask_all_text: true, |
|
||||||
mask_all_element_attributes: true, |
|
||||||
// This only triggers on page load, which for our SPA isn't particularly useful.
|
|
||||||
// Plus, the .capture call originating from somewhere in posthog makes it hard
|
|
||||||
// to redact URLs, which requires async code.
|
|
||||||
//
|
|
||||||
// To raise this manually, just call .capture("$pageview") or posthog.capture_pageview.
|
|
||||||
capture_pageview: false, |
|
||||||
sanitize_properties: this.sanitizeProperties, |
|
||||||
respect_dnt: true, |
|
||||||
}); |
|
||||||
this.enabled = true; |
|
||||||
} else { |
|
||||||
this.enabled = false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private sanitizeProperties = (properties: posthog.Properties): posthog.Properties => { |
|
||||||
// Callback from posthog to sanitize properties before sending them to the server.
|
|
||||||
//
|
|
||||||
// Here we sanitize posthog's built in properties which leak PII e.g. url reporting.
|
|
||||||
// See utils.js _.info.properties in posthog-js.
|
|
||||||
|
|
||||||
// Replace the $current_url with a redacted version.
|
|
||||||
// $redacted_current_url is injected by this class earlier in capture(), as its generation
|
|
||||||
// is async and can't be done in this non-async callback.
|
|
||||||
if (!properties['$redacted_current_url']) { |
|
||||||
console.log("$redacted_current_url not set in sanitizeProperties, will drop $current_url entirely"); |
|
||||||
} |
|
||||||
properties['$current_url'] = properties['$redacted_current_url']; |
|
||||||
delete properties['$redacted_current_url']; |
|
||||||
|
|
||||||
if (this.anonymity == Anonymity.Anonymous) { |
|
||||||
// drop referrer information for anonymous users
|
|
||||||
properties['$referrer'] = null; |
|
||||||
properties['$referring_domain'] = null; |
|
||||||
properties['$initial_referrer'] = null; |
|
||||||
properties['$initial_referring_domain'] = null; |
|
||||||
|
|
||||||
// drop device ID, which is a UUID persisted in local storage
|
|
||||||
properties['$device_id'] = null; |
|
||||||
} |
|
||||||
|
|
||||||
return properties; |
|
||||||
}; |
|
||||||
|
|
||||||
private static getAnonymityFromSettings(): Anonymity { |
|
||||||
// determine the current anonymity level based on current user settings
|
|
||||||
|
|
||||||
// "Send anonymous usage data which helps us improve Element. This will use a cookie."
|
|
||||||
const analyticsOptIn = SettingsStore.getValue("analyticsOptIn", null, true); |
|
||||||
|
|
||||||
// (proposed wording) "Send pseudonymous usage data which helps us improve Element. This will use a cookie."
|
|
||||||
//
|
|
||||||
// TODO: Currently, this is only a labs flag, for testing purposes.
|
|
||||||
const pseudonumousOptIn = SettingsStore.getValue("feature_pseudonymous_analytics_opt_in", null, true); |
|
||||||
|
|
||||||
let anonymity; |
|
||||||
if (pseudonumousOptIn) { |
|
||||||
anonymity = Anonymity.Pseudonymous; |
|
||||||
} else if (analyticsOptIn) { |
|
||||||
anonymity = Anonymity.Anonymous; |
|
||||||
} else { |
|
||||||
anonymity = Anonymity.Disabled; |
|
||||||
} |
|
||||||
|
|
||||||
return anonymity; |
|
||||||
} |
|
||||||
|
|
||||||
private registerSuperProperties(properties: posthog.Properties) { |
|
||||||
if (this.enabled) { |
|
||||||
this.posthog.register(properties); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private static async getPlatformProperties(): Promise<PlatformProperties> { |
|
||||||
const platform = PlatformPeg.get(); |
|
||||||
let appVersion; |
|
||||||
try { |
|
||||||
appVersion = await platform.getAppVersion(); |
|
||||||
} catch (e) { |
|
||||||
// this happens if no version is set i.e. in dev
|
|
||||||
appVersion = "unknown"; |
|
||||||
} |
|
||||||
|
|
||||||
return { |
|
||||||
appVersion, |
|
||||||
appPlatform: platform.getHumanReadableName(), |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
private async capture(eventName: string, properties: posthog.Properties) { |
|
||||||
if (!this.enabled) { |
|
||||||
return; |
|
||||||
} |
|
||||||
const { origin, hash, pathname } = window.location; |
|
||||||
properties['$redacted_current_url'] = await getRedactedCurrentLocation( |
|
||||||
origin, hash, pathname, this.anonymity); |
|
||||||
this.posthog.capture(eventName, properties); |
|
||||||
} |
|
||||||
|
|
||||||
public isEnabled(): boolean { |
|
||||||
return this.enabled; |
|
||||||
} |
|
||||||
|
|
||||||
public setAnonymity(anonymity: Anonymity): void { |
|
||||||
// Update this.anonymity.
|
|
||||||
// This is public for testing purposes, typically you want to call updateAnonymityFromSettings
|
|
||||||
// to ensure this value is in step with the user's settings.
|
|
||||||
if (this.enabled && (anonymity == Anonymity.Disabled || anonymity == Anonymity.Anonymous)) { |
|
||||||
// when transitioning to Disabled or Anonymous ensure we clear out any prior state
|
|
||||||
// set in posthog e.g. distinct ID
|
|
||||||
this.posthog.reset(); |
|
||||||
// Restore any previously set platform super properties
|
|
||||||
this.registerSuperProperties(this.platformSuperProperties); |
|
||||||
} |
|
||||||
this.anonymity = anonymity; |
|
||||||
} |
|
||||||
|
|
||||||
public async identifyUser(userId: string): Promise<void> { |
|
||||||
if (this.anonymity == Anonymity.Pseudonymous) { |
|
||||||
this.posthog.identify(await hashHex(userId)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public getAnonymity(): Anonymity { |
|
||||||
return this.anonymity; |
|
||||||
} |
|
||||||
|
|
||||||
public logout(): void { |
|
||||||
if (this.enabled) { |
|
||||||
this.posthog.reset(); |
|
||||||
} |
|
||||||
this.setAnonymity(Anonymity.Anonymous); |
|
||||||
} |
|
||||||
|
|
||||||
public async trackPseudonymousEvent<E extends IPseudonymousEvent>( |
|
||||||
eventName: E["eventName"], |
|
||||||
properties: E["properties"] = {}, |
|
||||||
) { |
|
||||||
if (this.anonymity == Anonymity.Anonymous || this.anonymity == Anonymity.Disabled) return; |
|
||||||
await this.capture(eventName, properties); |
|
||||||
} |
|
||||||
|
|
||||||
public async trackAnonymousEvent<E extends IAnonymousEvent>( |
|
||||||
eventName: E["eventName"], |
|
||||||
properties: E["properties"] = {}, |
|
||||||
): Promise<void> { |
|
||||||
if (this.anonymity == Anonymity.Disabled) return; |
|
||||||
await this.capture(eventName, properties); |
|
||||||
} |
|
||||||
|
|
||||||
public async trackRoomEvent<E extends IRoomEvent>( |
|
||||||
eventName: E["eventName"], |
|
||||||
roomId: string, |
|
||||||
properties: Omit<E["properties"], "roomId">, |
|
||||||
): Promise<void> { |
|
||||||
const updatedProperties = { |
|
||||||
...properties, |
|
||||||
hashedRoomId: roomId ? await hashHex(roomId) : null, |
|
||||||
}; |
|
||||||
await this.trackPseudonymousEvent(eventName, updatedProperties); |
|
||||||
} |
|
||||||
|
|
||||||
public async trackPageView(durationMs: number): Promise<void> { |
|
||||||
const hash = window.location.hash; |
|
||||||
|
|
||||||
let screen = null; |
|
||||||
const split = hash.split("/"); |
|
||||||
if (split.length >= 2) { |
|
||||||
screen = split[1]; |
|
||||||
} |
|
||||||
|
|
||||||
await this.trackAnonymousEvent<IPageView>("$pageview", { |
|
||||||
durationMs, |
|
||||||
screen, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
public async updatePlatformSuperProperties(): Promise<void> { |
|
||||||
// Update super properties in posthog with our platform (app version, platform).
|
|
||||||
// These properties will be subsequently passed in every event.
|
|
||||||
//
|
|
||||||
// This only needs to be done once per page lifetime. Note that getPlatformProperties
|
|
||||||
// is async and can involve a network request if we are running in a browser.
|
|
||||||
this.platformSuperProperties = await PosthogAnalytics.getPlatformProperties(); |
|
||||||
this.registerSuperProperties(this.platformSuperProperties); |
|
||||||
} |
|
||||||
|
|
||||||
public async updateAnonymityFromSettings(userId?: string): Promise<void> { |
|
||||||
// Update this.anonymity based on the user's analytics opt-in settings
|
|
||||||
// Identify the user (via hashed user ID) to posthog if anonymity is pseudonmyous
|
|
||||||
this.setAnonymity(PosthogAnalytics.getAnonymityFromSettings()); |
|
||||||
if (userId && this.getAnonymity() == Anonymity.Pseudonymous) { |
|
||||||
await this.identifyUser(userId); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,26 +0,0 @@ |
|||||||
/* |
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C. |
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
you may not use this file except in compliance with the License. |
|
||||||
You may obtain a copy of the License at |
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software |
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
See the License for the specific language governing permissions and |
|
||||||
limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
import SettingController from "./SettingController"; |
|
||||||
import { SettingLevel } from "../SettingLevel"; |
|
||||||
import { PosthogAnalytics } from "../../PosthogAnalytics"; |
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg"; |
|
||||||
|
|
||||||
export default class PseudonymousAnalyticsController extends SettingController { |
|
||||||
public onChange(level: SettingLevel, roomId: string, newValue: any) { |
|
||||||
PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId()); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,232 +0,0 @@ |
|||||||
/* |
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C. |
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
you may not use this file except in compliance with the License. |
|
||||||
You may obtain a copy of the License at |
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software |
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
See the License for the specific language governing permissions and |
|
||||||
limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
import { |
|
||||||
Anonymity, |
|
||||||
getRedactedCurrentLocation, |
|
||||||
IAnonymousEvent, |
|
||||||
IPseudonymousEvent, |
|
||||||
IRoomEvent, |
|
||||||
PosthogAnalytics, |
|
||||||
} from '../src/PosthogAnalytics'; |
|
||||||
|
|
||||||
import SdkConfig from '../src/SdkConfig'; |
|
||||||
|
|
||||||
class FakePosthog { |
|
||||||
public capture; |
|
||||||
public init; |
|
||||||
public identify; |
|
||||||
public reset; |
|
||||||
public register; |
|
||||||
|
|
||||||
constructor() { |
|
||||||
this.capture = jest.fn(); |
|
||||||
this.init = jest.fn(); |
|
||||||
this.identify = jest.fn(); |
|
||||||
this.reset = jest.fn(); |
|
||||||
this.register = jest.fn(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export interface ITestEvent extends IAnonymousEvent { |
|
||||||
key: "jest_test_event"; |
|
||||||
properties: { |
|
||||||
foo: string; |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
export interface ITestPseudonymousEvent extends IPseudonymousEvent { |
|
||||||
key: "jest_test_pseudo_event"; |
|
||||||
properties: { |
|
||||||
foo: string; |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
export interface ITestRoomEvent extends IRoomEvent { |
|
||||||
key: "jest_test_room_event"; |
|
||||||
properties: { |
|
||||||
foo: string; |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
describe("PosthogAnalytics", () => { |
|
||||||
let fakePosthog: FakePosthog; |
|
||||||
const shaHashes = { |
|
||||||
"42": "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049", |
|
||||||
"some": "a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b", |
|
||||||
"pii": "bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4", |
|
||||||
"foo": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", |
|
||||||
}; |
|
||||||
|
|
||||||
beforeEach(() => { |
|
||||||
fakePosthog = new FakePosthog(); |
|
||||||
|
|
||||||
window.crypto = { |
|
||||||
subtle: { |
|
||||||
digest: async (_, encodedMessage) => { |
|
||||||
const message = new TextDecoder().decode(encodedMessage); |
|
||||||
const hexHash = shaHashes[message]; |
|
||||||
const bytes = []; |
|
||||||
for (let c = 0; c < hexHash.length; c += 2) { |
|
||||||
bytes.push(parseInt(hexHash.substr(c, 2), 16)); |
|
||||||
} |
|
||||||
return bytes; |
|
||||||
}, |
|
||||||
}, |
|
||||||
}; |
|
||||||
}); |
|
||||||
|
|
||||||
afterEach(() => { |
|
||||||
window.crypto = null; |
|
||||||
}); |
|
||||||
|
|
||||||
describe("Initialisation", () => { |
|
||||||
it("Should not be enabled without config being set", () => { |
|
||||||
jest.spyOn(SdkConfig, "get").mockReturnValue({}); |
|
||||||
const analytics = new PosthogAnalytics(fakePosthog); |
|
||||||
expect(analytics.isEnabled()).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it("Should be enabled if config is set", () => { |
|
||||||
jest.spyOn(SdkConfig, "get").mockReturnValue({ |
|
||||||
posthog: { |
|
||||||
projectApiKey: "foo", |
|
||||||
apiHost: "bar", |
|
||||||
}, |
|
||||||
}); |
|
||||||
const analytics = new PosthogAnalytics(fakePosthog); |
|
||||||
analytics.setAnonymity(Anonymity.Pseudonymous); |
|
||||||
expect(analytics.isEnabled()).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe("Tracking", () => { |
|
||||||
let analytics: PosthogAnalytics; |
|
||||||
|
|
||||||
beforeEach(() => { |
|
||||||
jest.spyOn(SdkConfig, "get").mockReturnValue({ |
|
||||||
posthog: { |
|
||||||
projectApiKey: "foo", |
|
||||||
apiHost: "bar", |
|
||||||
}, |
|
||||||
}); |
|
||||||
|
|
||||||
analytics = new PosthogAnalytics(fakePosthog); |
|
||||||
}); |
|
||||||
|
|
||||||
it("Should pass trackAnonymousEvent() to posthog", async () => { |
|
||||||
analytics.setAnonymity(Anonymity.Pseudonymous); |
|
||||||
await analytics.trackAnonymousEvent<ITestEvent>("jest_test_event", { |
|
||||||
foo: "bar", |
|
||||||
}); |
|
||||||
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event"); |
|
||||||
expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); |
|
||||||
}); |
|
||||||
|
|
||||||
it("Should pass trackRoomEvent to posthog", async () => { |
|
||||||
analytics.setAnonymity(Anonymity.Pseudonymous); |
|
||||||
const roomId = "42"; |
|
||||||
await analytics.trackRoomEvent<IRoomEvent>("jest_test_event", roomId, { |
|
||||||
foo: "bar", |
|
||||||
}); |
|
||||||
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event"); |
|
||||||
expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); |
|
||||||
expect(fakePosthog.capture.mock.calls[0][1]["hashedRoomId"]) |
|
||||||
.toEqual("73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049"); |
|
||||||
}); |
|
||||||
|
|
||||||
it("Should pass trackPseudonymousEvent() to posthog", async () => { |
|
||||||
analytics.setAnonymity(Anonymity.Pseudonymous); |
|
||||||
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_pseudo_event", { |
|
||||||
foo: "bar", |
|
||||||
}); |
|
||||||
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_pseudo_event"); |
|
||||||
expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); |
|
||||||
}); |
|
||||||
|
|
||||||
it("Should not track pseudonymous messages if anonymous", async () => { |
|
||||||
analytics.setAnonymity(Anonymity.Anonymous); |
|
||||||
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_event", { |
|
||||||
foo: "bar", |
|
||||||
}); |
|
||||||
expect(fakePosthog.capture.mock.calls.length).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it("Should not track any events if disabled", async () => { |
|
||||||
analytics.setAnonymity(Anonymity.Disabled); |
|
||||||
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_event", { |
|
||||||
foo: "bar", |
|
||||||
}); |
|
||||||
await analytics.trackAnonymousEvent<ITestEvent>("jest_test_event", { |
|
||||||
foo: "bar", |
|
||||||
}); |
|
||||||
await analytics.trackRoomEvent<ITestRoomEvent>("room id", "foo", { |
|
||||||
foo: "bar", |
|
||||||
}); |
|
||||||
await analytics.trackPageView(200); |
|
||||||
expect(fakePosthog.capture.mock.calls.length).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it("Should pseudonymise a location of a known screen", async () => { |
|
||||||
const location = await getRedactedCurrentLocation( |
|
||||||
"https://foo.bar", "#/register/some/pii", "/", Anonymity.Pseudonymous); |
|
||||||
expect(location).toBe( |
|
||||||
`https://foo.bar/#/register/\ |
|
||||||
a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b/\ |
|
||||||
bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`);
|
|
||||||
}); |
|
||||||
|
|
||||||
it("Should anonymise a location of a known screen", async () => { |
|
||||||
const location = await getRedactedCurrentLocation( |
|
||||||
"https://foo.bar", "#/register/some/pii", "/", Anonymity.Anonymous); |
|
||||||
expect(location).toBe("https://foo.bar/#/register/<redacted>/<redacted>"); |
|
||||||
}); |
|
||||||
|
|
||||||
it("Should pseudonymise a location of an unknown screen", async () => { |
|
||||||
const location = await getRedactedCurrentLocation( |
|
||||||
"https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Pseudonymous); |
|
||||||
expect(location).toBe( |
|
||||||
`https://foo.bar/#/<redacted_screen_name>/\ |
|
||||||
a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b/\ |
|
||||||
bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`);
|
|
||||||
}); |
|
||||||
|
|
||||||
it("Should anonymise a location of an unknown screen", async () => { |
|
||||||
const location = await getRedactedCurrentLocation( |
|
||||||
"https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Anonymous); |
|
||||||
expect(location).toBe("https://foo.bar/#/<redacted_screen_name>/<redacted>/<redacted>"); |
|
||||||
}); |
|
||||||
|
|
||||||
it("Should handle an empty hash", async () => { |
|
||||||
const location = await getRedactedCurrentLocation( |
|
||||||
"https://foo.bar", "", "/", Anonymity.Anonymous); |
|
||||||
expect(location).toBe("https://foo.bar/"); |
|
||||||
}); |
|
||||||
|
|
||||||
it("Should identify the user to posthog if pseudonymous", async () => { |
|
||||||
analytics.setAnonymity(Anonymity.Pseudonymous); |
|
||||||
await analytics.identifyUser("foo"); |
|
||||||
expect(fakePosthog.identify.mock.calls[0][0]) |
|
||||||
.toBe("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"); |
|
||||||
}); |
|
||||||
|
|
||||||
it("Should not identify the user to posthog if anonymous", async () => { |
|
||||||
analytics.setAnonymity(Anonymity.Anonymous); |
|
||||||
await analytics.identifyUser("foo"); |
|
||||||
expect(fakePosthog.identify.mock.calls.length).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
Loading…
Reference in new issue