Skip to content

Commit

Permalink
Add useQueryString to InitOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
zikaari committed Jan 27, 2023
1 parent d2d0459 commit 69154c3
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 4 deletions.
6 changes: 6 additions & 0 deletions .changeset/fuzzy-baboons-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@segment/analytics-next': minor
'@segment/analytics-core': patch
---

Add useQueryString option to InitOptions
15 changes: 14 additions & 1 deletion packages/browser/src/core/analytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ export interface InitOptions {
plan?: Plan
retryQueue?: boolean
obfuscate?: boolean
/**
* Disables or sets constraints on processing of query string parameters
*/
useQueryString?:
| boolean
| {
aid?: RegExp
uid?: RegExp
}
}

/* analytics-classic stubs */
Expand Down Expand Up @@ -421,7 +430,11 @@ export class Analytics
return this._user.anonymousId(id)
}

async queryString(query: string): Promise<Context[]> {
async queryString(query: string): Promise<Context[] | void> {
if (this.options.useQueryString === false) {
return
}

const { queryString } = await import(
/* webpackChunkName: "queryString" */ '../query-string'
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import unfetch from 'unfetch'
import { AnalyticsBrowser } from '../../..'
import { createSuccess } from '../../../test-helpers/factories'

jest.mock('unfetch')
jest
.mocked(unfetch)
.mockImplementation(() => createSuccess({ integrations: {} }))

// @ts-ignore
delete window.location
// @ts-ignore
window.location = new URL(
'https://www.example.com?ajs_aid=873832VB&ajs_uid=xcvn7568'
)

describe('useQueryString configuration option', () => {
it('ignores aid and uid from query string when disabled', async () => {
const [analyticsAlt] = await AnalyticsBrowser.load(
{ writeKey: 'abc' },
{
useQueryString: false,
}
)

// not acknowledge the aid provided in the query params, let ajs generate one
expect(analyticsAlt.user().anonymousId()).not.toBe('873832VB')
expect(analyticsAlt.user().id()).toBe(null)
})

it('ignores uid when it doesnt match the required pattern', async () => {
const [analyticsAlt] = await AnalyticsBrowser.load(
{ writeKey: 'abc' },
{
useQueryString: {
uid: /[A-Z]{6}/,
},
}
)

// no constraint was set for aid therefore accepted
expect(analyticsAlt.user().anonymousId()).toBe('873832VB')
expect(analyticsAlt.user().id()).toBe(null)
})

it('accepts both aid and uid from query string when they match the required pattern', async () => {
const [analyticsAlt] = await AnalyticsBrowser.load(
{ writeKey: 'abc' },
{
useQueryString: {
aid: /\d{6}[A-Z]{2}/,
uid: /[a-z]{4}\d{4}/,
},
}
)

expect(analyticsAlt.user().anonymousId()).toBe('873832VB')
expect(analyticsAlt.user().id()).toBe('xcvn7568')
})
})
17 changes: 14 additions & 3 deletions packages/browser/src/core/query-string/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { pickPrefix } from './pickPrefix'
import { gracefulDecodeURIComponent } from './gracefulDecodeURIComponent'
import { Analytics } from '../analytics'
import { Context } from '../context'
import { isPlainObject } from '@segment/analytics-core'

export interface QueryStringParams {
[key: string]: string | null
Expand All @@ -23,22 +24,32 @@ export function queryString(
const calls = []

const { ajs_uid, ajs_event, ajs_aid } = params
const { aid: aidPattern = /.+/, uid: uidPattern = /.+/ } = isPlainObject(
analytics.options.useQueryString
)
? analytics.options.useQueryString
: {}

if (ajs_aid) {
const anonId = Array.isArray(params.ajs_aid)
? params.ajs_aid[0]
: params.ajs_aid

analytics.setAnonymousId(anonId)
if (aidPattern.test(anonId)) {
analytics.setAnonymousId(anonId)
}
}

if (ajs_uid) {
const uid = Array.isArray(params.ajs_uid)
? params.ajs_uid[0]
: params.ajs_uid
const traits = pickPrefix('ajs_trait_', params)

calls.push(analytics.identify(uid, traits))
if (uidPattern.test(uid)) {
const traits = pickPrefix('ajs_trait_', params)

calls.push(analytics.identify(uid, traits))
}
}

if (ajs_event) {
Expand Down
27 changes: 27 additions & 0 deletions packages/core/src/utils/__tests__/is-plain-object.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Spec derived from https://github.com/jonschlinkert/is-plain-object/blob/master/test/server.js

import { isPlainObject } from '../is-plain-object'

describe('isPlainObject', () => {
it('should return `true` if the object is created by the `Object` constructor.', function () {
expect(isPlainObject(Object.create({}))).toBe(true)
expect(isPlainObject(Object.create(Object.prototype))).toBe(true)
expect(isPlainObject({ foo: 'bar' })).toBe(true)
expect(isPlainObject({})).toBe(true)
expect(isPlainObject(Object.create(null))).toBe(true)
})

it('should return `false` if the object is not created by the `Object` constructor.', function () {
class Foo {
abc = {}
}

expect(isPlainObject(/foo/)).toBe(false)
expect(isPlainObject(function () {})).toBe(false)
expect(isPlainObject(1)).toBe(false)
expect(isPlainObject(['foo', 'bar'])).toBe(false)
expect(isPlainObject([])).toBe(false)
expect(isPlainObject(new Foo())).toBe(false)
expect(isPlainObject(null)).toBe(false)
})
})
26 changes: 26 additions & 0 deletions packages/core/src/utils/is-plain-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Code derived from https://github.com/jonschlinkert/is-plain-object/blob/master/is-plain-object.js

function isObject(o: unknown): o is Object {
return Object.prototype.toString.call(o) === '[object Object]'
}

export function isPlainObject(o: unknown): o is Record<PropertyKey, unknown> {
if (isObject(o) === false) return false

// If has modified constructor
const ctor = (o as any).constructor
if (ctor === undefined) return true

// If has modified prototype
const prot = ctor.prototype
if (isObject(prot) === false) return false

// If constructor does not have an Object-specific method
// eslint-disable-next-line no-prototype-builtins
if ((prot as Object).hasOwnProperty('isPrototypeOf') === false) {
return false
}

// Most likely a plain Object
return true
}

0 comments on commit 69154c3

Please sign in to comment.