@leanbase.com/js 0.1.1 → 0.1.2

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.
@@ -0,0 +1,222 @@
1
+ import { isFormData, isNull, isNullish, hasOwnProperty, isArray, isString, isNumber } from '@posthog/core'
2
+ import { Properties } from '../types'
3
+
4
+ const breaker: Breaker = {}
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
7
+ export type Breaker = {}
8
+
9
+ export const ArrayProto = Array.prototype
10
+ export const nativeForEach = ArrayProto.forEach
11
+ export const nativeIndexOf = ArrayProto.indexOf
12
+
13
+ const win: (Window & typeof globalThis) | undefined = typeof window !== 'undefined' ? window : undefined
14
+ const global: typeof globalThis | undefined = typeof globalThis !== 'undefined' ? globalThis : win
15
+ export const navigator = global?.navigator
16
+ export const document = global?.document
17
+ export const location = global?.location
18
+ export const fetch = global?.fetch
19
+ export const XMLHttpRequest =
20
+ global?.XMLHttpRequest && 'withCredentials' in new global.XMLHttpRequest() ? global.XMLHttpRequest : undefined
21
+ export const AbortController = global?.AbortController
22
+ export const userAgent = navigator?.userAgent
23
+ export { win as window }
24
+
25
+ export function eachArray<E = any>(
26
+ obj: E[] | null | undefined,
27
+ iterator: (value: E, key: number) => void | Breaker,
28
+ thisArg?: any
29
+ ): void {
30
+ if (isArray(obj)) {
31
+ if (nativeForEach && obj.forEach === nativeForEach) {
32
+ obj.forEach(iterator, thisArg)
33
+ } else if ('length' in obj && obj.length === +obj.length) {
34
+ for (let i = 0, l = obj.length; i < l; i++) {
35
+ if (i in obj && iterator.call(thisArg, obj[i], i) === breaker) {
36
+ return
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * @param {*=} obj
45
+ * @param {function(...*)=} iterator
46
+ * @param {Object=} thisArg
47
+ */
48
+ export function each(obj: any, iterator: (value: any, key: any) => void | Breaker, thisArg?: any): void {
49
+ if (isNullish(obj)) {
50
+ return
51
+ }
52
+ if (isArray(obj)) {
53
+ return eachArray(obj, iterator, thisArg)
54
+ }
55
+ if (isFormData(obj)) {
56
+ for (const pair of obj.entries()) {
57
+ if (iterator.call(thisArg, pair[1], pair[0]) === breaker) {
58
+ return
59
+ }
60
+ }
61
+ return
62
+ }
63
+ for (const key in obj) {
64
+ if (hasOwnProperty.call(obj, key)) {
65
+ if (iterator.call(thisArg, obj[key], key) === breaker) {
66
+ return
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ export const extend = function (obj: Record<string, any>, ...args: Record<string, any>[]): Record<string, any> {
73
+ eachArray(args, function (source) {
74
+ for (const prop in source) {
75
+ if (source[prop] !== void 0) {
76
+ obj[prop] = source[prop]
77
+ }
78
+ }
79
+ })
80
+ return obj
81
+ }
82
+
83
+ export const extendArray = function <T>(obj: T[], ...args: T[][]): T[] {
84
+ eachArray(args, function (source) {
85
+ eachArray(source, function (item) {
86
+ obj.push(item)
87
+ })
88
+ })
89
+ return obj
90
+ }
91
+
92
+ export const include = function (
93
+ obj: null | string | Array<any> | Record<string, any>,
94
+ target: any
95
+ ): boolean | Breaker {
96
+ let found = false
97
+ if (isNull(obj)) {
98
+ return found
99
+ }
100
+ if (nativeIndexOf && obj.indexOf === nativeIndexOf) {
101
+ return obj.indexOf(target) != -1
102
+ }
103
+ each(obj, function (value) {
104
+ if (found || (found = value === target)) {
105
+ return breaker
106
+ }
107
+ return
108
+ })
109
+ return found
110
+ }
111
+
112
+ /**
113
+ * Object.entries() polyfill
114
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
115
+ */
116
+ export function entries<T = any>(obj: Record<string, T>): [string, T][] {
117
+ const ownProps = Object.keys(obj)
118
+ let i = ownProps.length
119
+ const resArray = new Array(i) // preallocate the Array
120
+
121
+ while (i--) {
122
+ resArray[i] = [ownProps[i], obj[ownProps[i]]]
123
+ }
124
+ return resArray
125
+ }
126
+
127
+ export function addEventListener(
128
+ element: Window | Document | Element | undefined,
129
+ event: string,
130
+ callback: EventListener,
131
+ options?: AddEventListenerOptions
132
+ ): void {
133
+ const { capture = false, passive = true } = options ?? {}
134
+
135
+ // This is the only place where we are allowed to call this function
136
+ // because the whole idea is that we should be calling this instead of the built-in one
137
+ // eslint-disable-next-line posthog-js/no-add-event-listener
138
+ element?.addEventListener(event, callback, { capture, passive })
139
+ }
140
+
141
+ export const stripEmptyProperties = function (p: Properties): Properties {
142
+ const ret: Properties = {}
143
+ each(p, function (v, k) {
144
+ if ((isString(v) && v.length > 0) || isNumber(v)) {
145
+ ret[k] = v
146
+ }
147
+ })
148
+ return ret
149
+ }
150
+
151
+ const EXCLUDED_FROM_CROSS_SUBDOMAIN_COOKIE = ['herokuapp.com', 'vercel.app', 'netlify.app']
152
+ export function isCrossDomainCookie(documentLocation: Location | undefined) {
153
+ const hostname = documentLocation?.hostname
154
+
155
+ if (!isString(hostname)) {
156
+ return false
157
+ }
158
+ // split and slice isn't a great way to match arbitrary domains,
159
+ // but it's good enough for ensuring we only match herokuapp.com when it is the TLD
160
+ // for the hostname
161
+ const lastTwoParts = hostname.split('.').slice(-2).join('.')
162
+
163
+ for (const excluded of EXCLUDED_FROM_CROSS_SUBDOMAIN_COOKIE) {
164
+ if (lastTwoParts === excluded) {
165
+ return false
166
+ }
167
+ }
168
+
169
+ return true
170
+ }
171
+
172
+ /**
173
+ * Deep copies an object.
174
+ * It handles cycles by replacing all references to them with `undefined`
175
+ * Also supports customizing native values
176
+ *
177
+ * @param value
178
+ * @param customizer
179
+ * @returns {{}|undefined|*}
180
+ */
181
+ function deepCircularCopy<T extends Record<string, any> = Record<string, any>>(
182
+ value: T,
183
+ customizer?: <K extends keyof T = keyof T>(value: T[K], key?: K) => T[K]
184
+ ): T | undefined {
185
+ const COPY_IN_PROGRESS_SET = new Set()
186
+
187
+ function internalDeepCircularCopy(value: T, key?: string): T | undefined {
188
+ if (value !== Object(value)) return customizer ? customizer(value as any, key) : value // primitive value
189
+
190
+ if (COPY_IN_PROGRESS_SET.has(value)) return undefined
191
+ COPY_IN_PROGRESS_SET.add(value)
192
+ let result: T
193
+
194
+ if (isArray(value)) {
195
+ result = [] as any as T
196
+ eachArray(value, (it) => {
197
+ result.push(internalDeepCircularCopy(it))
198
+ })
199
+ } else {
200
+ result = {} as T
201
+ each(value, (val, key) => {
202
+ if (!COPY_IN_PROGRESS_SET.has(val)) {
203
+ ;(result as any)[key] = internalDeepCircularCopy(val, key)
204
+ }
205
+ })
206
+ }
207
+ return result
208
+ }
209
+ return internalDeepCircularCopy(value)
210
+ }
211
+
212
+ export function copyAndTruncateStrings<T extends Record<string, any> = Record<string, any>>(
213
+ object: T,
214
+ maxStringLength: number | null
215
+ ): T {
216
+ return deepCircularCopy(object, (value: any) => {
217
+ if (isString(value) && !isNull(maxStringLength)) {
218
+ return (value as string).slice(0, maxStringLength)
219
+ }
220
+ return value
221
+ }) as T
222
+ }
@@ -0,0 +1,128 @@
1
+ import { logger } from '../leanbase-logger'
2
+ import { each, document } from './'
3
+
4
+ import { isArray, isFile, isUndefined } from '@posthog/core'
5
+
6
+ const localDomains = ['localhost', '127.0.0.1']
7
+
8
+ /**
9
+ * IE11 doesn't support `new URL`
10
+ * so we can create an anchor element and use that to parse the URL
11
+ * there's a lot of overlap between HTMLHyperlinkElementUtils and URL
12
+ * meaning useful properties like `pathname` are available on both
13
+ */
14
+ export const convertToURL = (url: string): HTMLAnchorElement | null => {
15
+ const location = document?.createElement('a')
16
+ if (isUndefined(location)) {
17
+ return null
18
+ }
19
+
20
+ location.href = url
21
+ return location
22
+ }
23
+
24
+ export const formDataToQuery = function (formdata: Record<string, any> | FormData, arg_separator = '&'): string {
25
+ let use_val: string
26
+ let use_key: string
27
+ const tph_arr: string[] = []
28
+
29
+ each(formdata, function (val: File | string | undefined, key: string | undefined) {
30
+ // the key might be literally the string undefined for e.g. if {undefined: 'something'}
31
+ if (isUndefined(val) || isUndefined(key) || key === 'undefined') {
32
+ return
33
+ }
34
+
35
+ use_val = encodeURIComponent(isFile(val) ? val.name : val.toString())
36
+ use_key = encodeURIComponent(key)
37
+ tph_arr[tph_arr.length] = use_key + '=' + use_val
38
+ })
39
+
40
+ return tph_arr.join(arg_separator)
41
+ }
42
+
43
+ // NOTE: Once we get rid of IE11/op_mini we can start using URLSearchParams
44
+ export const getQueryParam = function (url: string, param: string): string {
45
+ const withoutHash: string = url.split('#')[0] || ''
46
+
47
+ // Split only on the first ? to sort problem out for those with multiple ?s
48
+ // and then remove them
49
+ const queryParams: string = withoutHash.split(/\?(.*)/)[1] || ''
50
+ const cleanedQueryParams = queryParams.replace(/^\?+/g, '')
51
+
52
+ const queryParts = cleanedQueryParams.split('&')
53
+ let keyValuePair
54
+
55
+ for (let i = 0; i < queryParts.length; i++) {
56
+ const parts = queryParts[i].split('=')
57
+ if (parts[0] === param) {
58
+ keyValuePair = parts
59
+ break
60
+ }
61
+ }
62
+
63
+ if (!isArray(keyValuePair) || keyValuePair.length < 2) {
64
+ return ''
65
+ } else {
66
+ let result = keyValuePair[1]
67
+ try {
68
+ result = decodeURIComponent(result)
69
+ } catch {
70
+ logger.error('Skipping decoding for malformed query param: ' + result)
71
+ }
72
+ return result.replace(/\+/g, ' ')
73
+ }
74
+ }
75
+
76
+ // replace any query params in the url with the provided mask value. Tries to keep the URL as instant as possible,
77
+ // including preserving malformed text in most cases
78
+ export const maskQueryParams = function <T extends string | undefined>(
79
+ url: T,
80
+ maskedParams: string[] | undefined,
81
+ mask: string
82
+ ): T extends string ? string : undefined {
83
+ if (!url || !maskedParams || !maskedParams.length) {
84
+ return url as any
85
+ }
86
+
87
+ const splitHash = url.split('#')
88
+ const withoutHash: string = splitHash[0] || ''
89
+ const hash = splitHash[1]
90
+
91
+ const splitQuery: string[] = withoutHash.split('?')
92
+ const queryString: string = splitQuery[1]
93
+ const urlWithoutQueryAndHash: string = splitQuery[0]
94
+ const queryParts = (queryString || '').split('&')
95
+
96
+ // use an array of strings rather than an object to preserve ordering and duplicates
97
+ const paramStrings: string[] = []
98
+
99
+ for (let i = 0; i < queryParts.length; i++) {
100
+ const keyValuePair = queryParts[i].split('=')
101
+ if (!isArray(keyValuePair)) {
102
+ continue
103
+ } else if (maskedParams.includes(keyValuePair[0])) {
104
+ paramStrings.push(keyValuePair[0] + '=' + mask)
105
+ } else {
106
+ paramStrings.push(queryParts[i])
107
+ }
108
+ }
109
+
110
+ let result = urlWithoutQueryAndHash
111
+ if (queryString != null) {
112
+ result += '?' + paramStrings.join('&')
113
+ }
114
+ if (hash != null) {
115
+ result += '#' + hash
116
+ }
117
+
118
+ return result as any
119
+ }
120
+
121
+ export const _getHashParam = function (hash: string, param: string): string | null {
122
+ const matches = hash.match(new RegExp(param + '=([^&]*)'))
123
+ return matches ? matches[1] : null
124
+ }
125
+
126
+ export const isLocalhost = (): boolean => {
127
+ return localDomains.includes(location.hostname)
128
+ }
@@ -0,0 +1,27 @@
1
+ export class SimpleEventEmitter {
2
+ private _events: { [key: string]: ((...args: any[]) => void)[] } = {}
3
+
4
+ constructor() {
5
+ this._events = {}
6
+ }
7
+
8
+ on(event: string, listener: (...args: any[]) => void): () => void {
9
+ if (!this._events[event]) {
10
+ this._events[event] = []
11
+ }
12
+ this._events[event].push(listener)
13
+
14
+ return () => {
15
+ this._events[event] = this._events[event].filter((x) => x !== listener)
16
+ }
17
+ }
18
+
19
+ emit(event: string, payload: any): void {
20
+ for (const listener of this._events[event] || []) {
21
+ listener(payload)
22
+ }
23
+ for (const listener of this._events['*'] || []) {
24
+ listener(event, payload)
25
+ }
26
+ }
27
+ }