@edtechimpact/analytics-sdk
v1.2.1
Published
Web SDK for EdTech Impact Analytics - Capture user activity data for education applications
Readme
EdTech Impact Analytics SDK
A lightweight, TypeScript-first SDK for capturing user activity data in education applications. Sends events to the EdTech Impact Analytics API v1.1.
Features
- 🚀 Lightweight — < 5KB gzipped core bundle
- 📝 TypeScript-first — Full type definitions included
- 💾 Offline resilience — Events are queued and persisted to localStorage
- 🔁 Automatic retries — Exponential backoff for failed requests
- ⚛️ React integration — Hooks and context provider included
- 🔒 Privacy by design — Query params and fragments stripped from URLs automatically
Installation
npm
npm install @edtechimpact/analytics-sdkyarn
yarn add @edtechimpact/analytics-sdkbun
bun add @edtechimpact/analytics-sdkQuick Start
Basic Usage
import { EdTechImpact } from '@edtechimpact/analytics-sdk';
// 1. Initialize once at app startup
EdTechImpact.init({
apiKey: 'your-api-key',
product_id: 'your-product-id',
defaultSchoolId: 'sch_001', // Optional fallback for single-school deployments
debug: false, // Enable for console logging
});
// 2. Identify the user and track login
EdTechImpact.login('[email protected]', {
schoolId: 'sch_123',
actor: {
role: 'student',
class_id: '5B',
grade_index: 5,
},
});
// 3. Track custom events
EdTechImpact.track('completed', {
activity: {
id: 'quiz-456',
name: 'English Chapter Test',
type: 'quiz',
subject: 'english',
difficulty: 'intermediate',
},
result: {
success: true,
score: 0.95, // 0.0 – 1.0
duration_ms: 900000,
},
});
// 4. Track page views (query params and fragments stripped automatically)
EdTechImpact.page(window.location.href, { title: document.title });
// 5. Log the user out
EdTechImpact.logout();React Integration
import { EdTechImpactProvider, useEdTechImpact } from '@edtechimpact/analytics-sdk/react';
// Wrap your app with the provider
function App() {
return (
<EdTechImpactProvider
apiKey="your-api-key"
product_id="your-product-id"
defaultSchoolId="sch_001"
>
<MyApp />
</EdTechImpactProvider>
);
}
// Identify and log in after authentication
function LoginButton() {
const { login } = useEdTechImpact();
const handleLogin = async (user) => {
await authenticateUser(user);
login(user.id, {
schoolId: user.schoolId,
actor: { role: 'student', class_id: user.classId },
});
};
return <button onClick={handleLogin}>Login</button>;
}
// Track course completion
function CompleteButton({ courseId, courseName }) {
const { track } = useEdTechImpact();
return (
<button
onClick={() =>
track('completed', {
activity: { id: courseId, name: courseName, type: 'lesson' },
result: { success: true, score: 0.85, duration_ms: 300000 },
})
}
>
Complete Course
</button>
);
}API Reference
Initialization
EdTechImpact.init(config)
Initialize the SDK. Must be called once before any other method.
EdTechImpact.init({
apiKey: 'your-api-key', // Required
product_id: 'your-product-id', // Required
defaultSchoolId: 'sch_001', // Optional — fallback school_id when not set per-user
endpoint: 'https://...', // Optional — override API endpoint
debug: false, // Optional — verbose console logging
batchSize: 10, // Optional — events per batch (default: 10)
flushInterval: 5000, // Optional — auto-flush interval in ms (default: 5000)
maxRetries: 3, // Optional — retry attempts on failure (default: 3)
persistEvents: true, // Optional — persist queue to localStorage (default: true)
source: 'web-sdk', // Optional — source tag for deduplication
});User Identification
EdTechImpact.identify(userId, options?)
Identifies the current user without emitting a login event. Use login() for the full login flow.
EdTechImpact.identify('[email protected]', {
schoolId: 'sch_123', // Sets school_id on all subsequent events
actor: {
role: 'student', // 'student' | 'teacher' | 'admin' | 'system'
class_id: '5B',
grade_index: 5,
education_level: 'KS2',
institution_id: 'sch_123',
national: { upn: 'A123456789012' },
},
});EdTechImpact.login(userId, options?)
Identifies the user and tracks a session_started event. This is the recommended method for handling logins.
EdTechImpact.login('[email protected]', {
schoolId: 'sch_123',
actor: { role: 'student', class_id: '5B' },
});EdTechImpact.logout()
Tracks a session_ended event, flushes pending events, and clears user identity.
EdTechImpact.logout();Event Tracking
EdTechImpact.track(eventName, properties?)
Track a custom event. Events are batched and sent automatically.
// Simple event
EdTechImpact.track('launched', {
activity: { id: 'lesson-123', name: 'Math Lesson 1', type: 'lesson' },
});
// Event with result
EdTechImpact.track('answered', {
activity: { id: 'question-456', type: 'question' },
result: {
correct: true,
response: '4',
score: 1.0,
duration_ms: 12000,
},
event_kind: 'interaction',
});
// Event with custom attributes
EdTechImpact.track('completed', {
activity: {
id: 'quiz-789',
type: 'quiz',
subject: 'mathematics',
difficulty: 'intermediate',
ai: false,
attempt: 1,
},
result: {
success: true,
score: 0.85,
duration_ms: 180000,
},
attributes: {
lesson_format: 'video',
ai_assisted: false,
},
});activity fields:
| Field | Type | Description |
|-------|------|-------------|
| id | string | Your internal activity identifier |
| name | string | Human-readable activity name |
| type | string | lesson | quiz | question | video | assessment | chat |
| subject | string | Subject area (e.g. "mathematics") |
| topic | string | Topic within the subject |
| difficulty | string | beginner | intermediate | advanced |
| ai | boolean | Whether the activity uses AI-generated content |
| attempt | number | Attempt number (1-indexed) |
result fields:
| Field | Type | Description |
|-------|------|-------------|
| success | boolean | Whether the activity was completed successfully |
| correct | boolean | Whether the answer was correct |
| score | number | Scaled score 0.0 – 1.0 |
| response | string | Raw response text |
| duration_ms | number | Time taken in milliseconds |
| progress | number | Progress through the activity 0.0 – 1.0 |
EdTechImpact.page(url?, properties?)
Track a page view. Query params and URL fragments are stripped automatically before sending.
// Browser — URL inferred from window.location.href
EdTechImpact.page();
// SPA route change
EdTechImpact.page(window.location.href, { title: document.title });
// With curricular context
EdTechImpact.page(window.location.href, {
title: 'Course Overview',
course_id: 'course_eng_ks4',
assignment_id: 'assign_789',
});EdTechImpact.reset()
Clears user identity without flushing events.
EdTechImpact.reset();EdTechImpact.flush()
Forces all queued events to be sent immediately.
await EdTechImpact.flush();Utility Methods
EdTechImpact.getUserId(); // Returns current user ID or null
EdTechImpact.isInitialized(); // Returns true if init() has been called
EdTechImpact.isDebugEnabled(); // Returns true if debug mode is on
EdTechImpact.destroy(); // Stops timers and clears all stateReact Hooks
useEdTechImpact()
Access all SDK methods in any component inside EdTechImpactProvider.
const {
identify,
login,
logout,
track,
page,
reset,
flush,
getUserId,
isInitialized,
} = useEdTechImpact();useTrackEvent(eventName)
Returns a stable, memoised callback for a specific event. Useful when passing track functions as props.
const trackAnswer = useTrackEvent('answered');
trackAnswer({
activity: { id: 'q1', type: 'question' },
result: { correct: true, response: 'A' },
});TypeScript
The SDK is written in TypeScript with full type definitions exported.
import type {
EdTechImpactConfig,
ETIEvent,
ActorDomain,
ActivityDomain,
ResultDomain,
ClientDomain,
ContextDomain,
TrackProperties,
PageViewProperties,
APISuccessResponse,
APIRejectionError,
} from '@edtechimpact/analytics-sdk';school_id Resolution
Every event requires a school_id. The SDK resolves it in this order:
schoolIdpassed toidentify()orlogin()config.defaultSchoolIdset ininit()""— the API will reject events with an emptyschool_id
For single-school deployments (e.g. a Chrome extension), set defaultSchoolId in init() and omit it from identify()/login().
Privacy
The SDK is designed with privacy in mind:
- No fingerprinting — Only captures explicitly provided identifiers
- No content inspection — Does not capture page content or keystrokes
- URL sanitisation — Query parameters and fragments are stripped from page URLs before sending
- User-controlled — Events are only sent after a user is identified
- Offline-first — Events are stored locally until successfully delivered
Error Handling
- Automatic retries — Failed requests retry with exponential backoff (default: 3 attempts)
- Partial rejections — A 200 response may include
rejected > 0; checkresponse.errorsfor per-event details - Offline queue — Events are persisted to localStorage and replayed on next load
- Silent failures — SDK methods catch errors internally and log them in debug mode
Browser Support
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
Development
# Install dependencies
bun install
# Build (generates ESM, CJS, and TypeScript declarations)
bun run build
# Run tests
bun test
# Run tests in watch mode
bun run test:watch
# Type check
bun run typecheck
# Lint and format
bun run check:fixPublishing
# Dry run to verify package contents
npm pack
# Publish to npm (requires authentication)
npm publishBuild outputs:
| File | Format | Description |
|------|--------|-------------|
| dist/index.js | ESM | Core SDK |
| dist/index.cjs | CJS | Core SDK |
| dist/react/index.js | ESM | React integration |
| dist/react/index.cjs | CJS | React integration |
| dist/index.d.ts | Types | Core declarations |
| dist/react/index.d.ts | Types | React declarations |
