npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@june-so/analytics-next

v2.0.0

Published

This is June's JS SDK, based on Analytics-Next by Segment.

Downloads

55,462

Readme

This is June's JS SDK, based on Analytics-Next by Segment.

Getting Started

  • make dev should start a development server after a yarn install.
  • The writeKey is a valid June API key. You can make one in interim by going into the database (api_keys table) and inserting a new row.

Components

  • Tester (example): NextJS app used to generate and emit mock events to a test or production event ingress.
  • Analytics.js (src): Analytics library source for both brower, NodeJS

Understanding Analytics-Next

To start understanding this codebase, you need to look at a couple crucial folders within src:

  • plugins: analytics-next is implemented in a modular fashion, so there’s many plugins that perform separate functions.
    • To be honest, the most important plugins are segmentio and analytics-node.
    • The segmentio plugin practically handles event sending for browser clients.
    • The analytics-node plugin handles event sending for Node clients.
    • These plugins are both enabled in the entrypoint for the library, although e.g. configuration settings to both don’t necessarily get passed down to them raw.

Testing Locally

The Easy Way (Tester App)

The tester app provides a convenient way to test out small changes to the Analytics-Next codebase, reloading your changes on the fly.

To use the tester app, configure sdk/example/config.js and change whether to use the local or remote API.

The SDK tester app is ran automatically by Docker Compose. You should be able to visit at localhost:3002.

The local remote for Analytics-Node is different, as the tester is proxied via Docker. See the configuration file for more information.

Standalone Build

To change API endpoints for the standalone build, a full rebuild must be issued with the endpoint changes. There’s currently no mechanism to change apiHost on the fly (although, considering it uses the same code as the NPM build, this shouldn’t be tested separately anyways).

Browser (React, NPM)

To test the browser build, pass the apiHost parameter when instantiating. An example:

// Import June SDK:
import { AnalyticsBrowser } from '@june-so/analytics-next'

// Near the entrypoint of your app, instantiate AnalyticsBrowser:
let [analytics] = await AnalyticsBrowser.load({
  writeKey: 'MY-API-KEY',
  apiHost: 'http://localhost:3001',
})

Node (NPM)

To test the Node build, pass the apiHost and httpScheme parameter when instantiating. An example:

// Import June SDK:
import { AnalyticsNode } from '@june-so/analytics-next'

// Instantiate AnalyticsNode:
const [nodeAnalytics] = await AnalyticsNode.load({
  writeKey: 'MY-KEY',
  httpScheme: 'http',
  apiHost: 'localhost:3001/sdk',
})

Building & Publishing

  • The build process emits a couple of files, useful for production:
    • The build file used in the browser script is dist/umd/standalone.js. With some additional code (slightly cloned) from the official Segment documentation, we can easily activate this in the client using a snippet:
      <script>
          window.analytics = {};
          function juneify(writeKey) {
              window.analytics._writeKey = writeKey;
              var script = document.createElement("script");
              script.type = "application/javascript";
              script.onload = function () {
                  window.analytics.page();
              }
              script.src = "https://unpkg.com/@june-so/analytics-next/dist/umd/standalone.js";
              var first = document.getElementsByTagName('script')[0];
              first.parentNode.insertBefore(script, first);
          }
          juneify("API_KEY");
      </script>
    • Then there’s the NPM package, alongside any custom scripts.
      • scripts/sdk_release should publish the package to NPM.
      • You’ll need publish credentials (see https://github.com/juneHQ/june/issues/2837)

Annotated Source

The core event emission code is distributed between two plugins: segmentio and analytics-node. Here are some documented changes that make analytics-next send events to June:

segmentio/fetch-dispatcher.ts

function dispatch(url: string, body: object): Promise<unknown> {
  return fetch(url, {
    headers: { 'Content-Type': 'application/json' }, // <----
    method: 'post',
    body: JSON.stringify(body),
  })
}

An important change from vanilla is the changing of content-type: the original Analytics-Next sends events in plaintext by default. Instead, vanilla code will silently error out.

segmentio/batched-dispatcher.ts

export default function batch(apiHost: string, config?: BatchingConfig) {
...
}

Some changes to note in the batched dispatcher include application/json content types, alongside a crucial bit: changing the apiHost from a hardcoded Segment API link to June’s endpoint (around L65 and some repetitions in the file).

We’ve never seen this dispatcher used, but keep it updated for compatibility purposes. The next file is the most important for browser usage.

segmentio/index.ts

This plugin defines all visible behavior of the browser integration. This is where the critical bits lie: there are custom type definitions for June’s SDK settings (most notably the addition of an apiHost parameter not present in vanilla).

export function segmentio(
  analytics: Analytics,
  settings?: SegmentioSettings,
  integrations?: LegacySettings['integrations']
): Plugin {
  const buffer = new PersistedPriorityQueue(
    analytics.queue.queue.maxAttempts,
    `dest-Segment.io`
  )
  const flushing = false

  const apiHost = settings?.apiHost ?? 'api.june.so/sdk'
  const remote = apiHost.includes('localhost')
    ? `http://${apiHost}`
    : `https://${apiHost}`

  const client =
    settings?.deliveryStrategy?.strategy === 'batching'
      ? batch(apiHost, settings?.deliveryStrategy?.config)
      : standard()

  async function send(ctx: Context): Promise<Context> {
    if (isOffline()) {
      buffer.push(ctx)
      // eslint-disable-next-line @typescript-eslint/no-use-before-define
      scheduleFlush(flushing, buffer, segmentio, scheduleFlush)
      return ctx
    }

    const path = ctx.event.type //.charAt(0)
    let json = toFacade(ctx.event).json()

    if (ctx.event.type === 'track') {
      delete json.traits
    }

    if (ctx.event.type === 'alias') {
      json = onAlias(analytics, json)
    }

    return client
      .dispatch(
        `${remote}/${path}`,
        normalize(analytics, json, settings, integrations)
      )
      .then(() => ctx)
      .catch((err) => {
        if (err.type === 'error' || err.message === 'Failed to fetch') {
          buffer.push(ctx)
          // eslint-disable-next-line @typescript-eslint/no-use-before-define
          scheduleFlush(flushing, buffer, segmentio, scheduleFlush)
        }
        return ctx
      })
  }

  const segmentio: Plugin = {
    name: 'Segment.io',
    type: 'after',
    version: '0.1.0',
    isLoaded: (): boolean => true,
    load: (): Promise<void> => Promise.resolve(),
    track: send,
    identify: send,
    page: send,
    alias: send,
    group: send,
  }

  return segmentio
}

Aside from configuration changes, the most important line that enables sending to our SDK endpoints is the following:

const apiHost = settings?.apiHost ?? 'api.june.so/sdk'
const remote = apiHost.includes('localhost')
  ? `http://${apiHost}`
  : `https://${apiHost}`

When passing in settings and initializing the SDK, you can pass an apiHost to test locally. This also works with the tester app.

analytics-node/index.ts

Most changes to this SDK are almost identical to the browser changes (content-type, etc).

The Node SDK has some modifications in the type signature for configuration parameters: most notably (and usefully), you can pass in an apiHost and httpScheme. This allows for local testing.

standalone.ts & standalone-analytics.ts

These files contain the code that gets run for the Standalone build (that is, the script tag we provide for easy installs).

The most important thing to make sure when loading the browser script is to initialize window.analytics to an empty object and then set window.analytics._writeKey with the API key.

analytics.ts

This file contains the API surface for the analytics package, which is standard (the only thing that changes is the underlying dispatcher).

analytics-node.ts & browser.ts

This file contains the modules imported when the NPM package is installed. There’s some modified type signatures, in particular to make sure passing an httpScheme and apiHost is allowed.