@rooguys/js 0.1.0

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.
package/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # Rooguys Javascript SDK
2
+
3
+ The official Browser SDK for the Rooguys Gamification API. Lightweight and dependency-free (uses native `fetch`).
4
+
5
+ ## Installation
6
+
7
+ ### NPM
8
+ ```bash
9
+ npm install @rooguys/js
10
+ ```
11
+
12
+ ### Direct Import (ES Modules)
13
+ ```javascript
14
+ import Rooguys from './path/to/sdk/index.js';
15
+ ```
16
+
17
+ ## Initialization
18
+
19
+ ```javascript
20
+ import Rooguys from '@rooguys/js';
21
+
22
+ const client = new Rooguys('YOUR_API_KEY', {
23
+ baseUrl: 'https://api.rooguys.com/v1',
24
+ });
25
+ ```
26
+
27
+ ## Usage Examples
28
+
29
+ ### 1. Track an Event
30
+
31
+ ```javascript
32
+ client.events.track('level_completed', 'user_123', { difficulty: 'hard' })
33
+ .then(response => {
34
+ console.log('Event tracked:', response);
35
+ })
36
+ .catch(console.error);
37
+ ```
38
+
39
+ ### 2. Get User Profile
40
+
41
+ ```javascript
42
+ async function showProfile(userId) {
43
+ try {
44
+ const user = await client.users.get(userId);
45
+ document.getElementById('points').innerText = user.points;
46
+ document.getElementById('level').innerText = user.level?.name || 'No Level';
47
+ } catch (error) {
48
+ console.error('Failed to load profile:', error);
49
+ }
50
+ }
51
+ ```
52
+
53
+ ### 3. Display Leaderboard
54
+
55
+ ```javascript
56
+ async function loadLeaderboard() {
57
+ const data = await client.leaderboards.getGlobal('weekly');
58
+
59
+ const list = document.getElementById('leaderboard');
60
+ list.innerHTML = data.rankings
61
+ .map(r => `<li>#${r.rank} ${r.user_id}: ${r.points}</li>`)
62
+ .join('');
63
+ }
64
+ ```
65
+
66
+ ### 4. List Custom Leaderboards
67
+
68
+ ```javascript
69
+ // Get all available leaderboards
70
+ const leaderboards = await client.leaderboards.list(1, 20);
71
+
72
+ leaderboards.data.forEach(lb => {
73
+ console.log(`${lb.name}: ${lb.description}`);
74
+ });
75
+ ```
76
+
77
+ ### 5. Get Custom Leaderboard Rankings
78
+
79
+ ```javascript
80
+ // Get rankings for a specific leaderboard
81
+ const customLb = await client.leaderboards.getCustom('leaderboard_id', 1, 10);
82
+
83
+ console.log(`Leaderboard: ${customLb.name}`);
84
+ customLb.rankings.forEach(entry => {
85
+ console.log(`#${entry.rank} - ${entry.user_id}: ${entry.points} pts`);
86
+ });
87
+ ```
88
+
89
+ ### 6. Get User Rank in Custom Leaderboard
90
+
91
+ ```javascript
92
+ const rank = await client.leaderboards.getUserRank('leaderboard_id', 'user_123');
93
+
94
+ console.log(`User rank: #${rank.rank} out of ${rank.total_users}`);
95
+ ```
96
+
97
+ ### 7. List Badges
98
+
99
+ ```javascript
100
+ // Get all active badges
101
+ const badges = await client.badges.list(1, 50, true);
102
+
103
+ badges.data.forEach(badge => {
104
+ console.log(`${badge.name}: ${badge.description}`);
105
+ });
106
+ ```
107
+
108
+ ### 8. List Levels
109
+
110
+ ```javascript
111
+ const levels = await client.levels.list();
112
+
113
+ levels.data.forEach(level => {
114
+ console.log(`Level ${level.level_number}: ${level.name} (${level.points_required} pts)`);
115
+ });
116
+ ```
117
+
118
+ ### 9. Get Questionnaire
119
+
120
+ ```javascript
121
+ // Get questionnaire by slug
122
+ const questionnaire = await client.questionnaires.get('onboarding-survey');
123
+
124
+ console.log(`Title: ${questionnaire.title}`);
125
+ questionnaire.questions.forEach(q => {
126
+ console.log(`Q: ${q.text}`);
127
+ });
128
+ ```
129
+
130
+ ### 10. Get Active Questionnaire
131
+
132
+ ```javascript
133
+ const activeQuestionnaire = await client.questionnaires.getActive();
134
+
135
+ if (activeQuestionnaire) {
136
+ console.log(`Active: ${activeQuestionnaire.title}`);
137
+ }
138
+ ```
139
+
140
+ ### 11. Aha Score - Declare User Activation
141
+
142
+ Track when users reach their "Aha Moment" with declarative scores (1-5).
143
+
144
+ ```javascript
145
+ // Declare that a user has reached an activation milestone
146
+ const result = await client.aha.declare('user_123', 4);
147
+
148
+ console.log(result.message); // "Aha score declared successfully"
149
+ ```
150
+
151
+ ### 12. Aha Score - Get User Score
152
+
153
+ Retrieve a user's Aha Score, including declarative and inferred scores.
154
+
155
+ ```javascript
156
+ const ahaScore = await client.aha.getUserScore('user_123');
157
+
158
+ console.log(`Current Score: ${ahaScore.data.current_score}`);
159
+ console.log(`Status: ${ahaScore.data.status}`); // 'not_started', 'progressing', or 'activated'
160
+ console.log(`Declarative Score: ${ahaScore.data.declarative_score}`);
161
+ console.log(`Inferred Score: ${ahaScore.data.inferred_score}`);
162
+
163
+ // Access history
164
+ if (ahaScore.data.history.initial) {
165
+ console.log(`Initial Score: ${ahaScore.data.history.initial}`);
166
+ console.log(`Initial Date: ${ahaScore.data.history.initial_date}`);
167
+ }
168
+ ```
169
+
170
+ ## API Reference
171
+
172
+ ### Events
173
+
174
+ - `track(eventName, userId, properties, options)` - Track a user event
175
+
176
+ ### Users
177
+
178
+ - `get(userId)` - Get user profile
179
+ - `getBulk(userIds)` - Get multiple user profiles
180
+ - `getBadges(userId)` - Get user's badges
181
+ - `getRank(userId, timeframe)` - Get user's rank in global leaderboard
182
+ - `submitAnswers(userId, questionnaireId, answers)` - Submit questionnaire answers
183
+
184
+ ### Leaderboards
185
+
186
+ - `getGlobal(timeframe, page, limit)` - Get global leaderboard rankings
187
+ - `list(page, limit, search)` - List all available leaderboards
188
+ - `getCustom(leaderboardId, page, limit, search)` - Get custom leaderboard rankings
189
+ - `getUserRank(leaderboardId, userId)` - Get user's rank in a custom leaderboard
190
+
191
+ ### Badges
192
+
193
+ - `list(page, limit, activeOnly)` - List all badges
194
+
195
+ ### Levels
196
+
197
+ - `list(page, limit)` - List all levels
198
+
199
+ ### Questionnaires
200
+
201
+ - `get(slug)` - Get questionnaire by slug
202
+ - `getActive()` - Get the currently active questionnaire
203
+
204
+ ### Aha Score
205
+
206
+ - `declare(userId, value)` - Declare a user's Aha Moment score (value must be between 1-5)
207
+ - `getUserScore(userId)` - Retrieve a user's current Aha Score and history
208
+
209
+ ## Error Handling
210
+
211
+ ```javascript
212
+ try {
213
+ await client.users.get('unknown_user');
214
+ } catch (error) {
215
+ console.error('API Error:', error.message);
216
+ }
217
+ ```
218
+
219
+ ### Validation Errors
220
+
221
+ ```javascript
222
+ try {
223
+ // Invalid Aha Score value (must be 1-5)
224
+ await client.aha.declare('user_123', 10);
225
+ } catch (error) {
226
+ console.error(error.message); // "Aha score value must be between 1 and 5"
227
+ }
228
+ ```
229
+
230
+ ## Testing
231
+
232
+ The SDK includes comprehensive test coverage with both unit tests and property-based tests.
233
+
234
+ ### Running Tests
235
+
236
+ ```bash
237
+ npm test
238
+ ```
239
+
240
+ ### Test Coverage
241
+
242
+ ```bash
243
+ npm run test:coverage
244
+ ```
245
+
246
+ The SDK maintains >90% test coverage across all modules, including:
247
+ - Unit tests for all API methods
248
+ - Property-based tests using fast-check
249
+ - Error handling and edge case validation
250
+ - Concurrent request handling
251
+
252
+ ### Property-Based Testing
253
+
254
+ The SDK uses [fast-check](https://github.com/dubzzz/fast-check) for property-based testing to verify correctness across a wide range of inputs:
255
+
256
+ ```javascript
257
+ // Example: Verifying response parsing preservation
258
+ fc.assert(
259
+ fc.property(
260
+ fc.jsonValue(),
261
+ async (responseData) => {
262
+ // Test that any valid JSON response is parsed correctly
263
+ // and data structure is preserved
264
+ }
265
+ ),
266
+ { numRuns: 100 }
267
+ );
268
+ ```
269
+
270
+ ## Requirements
271
+
272
+ This SDK requires a browser environment with support for:
273
+ - `fetch` API
274
+ - `Promise`
275
+ - `AbortController` (for timeouts)
276
+
277
+ Most modern browsers support these features.
package/jest.config.js ADDED
@@ -0,0 +1,22 @@
1
+ export default {
2
+ testEnvironment: 'jsdom',
3
+ roots: ['<rootDir>/src'],
4
+ testMatch: ['**/__tests__/**/*.test.js'],
5
+ transform: {},
6
+ collectCoverageFrom: [
7
+ 'src/**/*.js',
8
+ '!src/**/*.test.js',
9
+ '!src/**/__tests__/**',
10
+ ],
11
+ coverageThreshold: {
12
+ global: {
13
+ lines: 90,
14
+ branches: 85,
15
+ functions: 90,
16
+ statements: 90,
17
+ },
18
+ },
19
+ coverageDirectory: 'coverage',
20
+ coverageReporters: ['text', 'lcov', 'html'],
21
+ setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.js'],
22
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@rooguys/js",
3
+ "version": "0.1.0",
4
+ "description": "Official Browser SDK for Rooguys API",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "module": "src/index.js",
8
+ "scripts": {
9
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest",
10
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
11
+ "test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage"
12
+ },
13
+ "keywords": [
14
+ "rooguys",
15
+ "sdk",
16
+ "browser",
17
+ "gamification"
18
+ ],
19
+ "author": "mowamed",
20
+ "license": "MIT",
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "devDependencies": {
25
+ "@types/jest": "^29.5.0",
26
+ "fast-check": "^3.15.0",
27
+ "jest": "^29.7.0",
28
+ "jest-environment-jsdom": "^29.7.0"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/Rooguys/rooguys-browser-sdk.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/Rooguys/rooguys-browser-sdk/issues"
36
+ },
37
+ "homepage": "https://github.com/Rooguys/rooguys-browser-sdk#readme"
38
+ }
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Mock API response fixtures for testing
3
+ */
4
+
5
+ export const mockResponses = {
6
+ userProfile: {
7
+ user_id: 'user123',
8
+ points: 100,
9
+ persona: 'Achiever',
10
+ level: {
11
+ id: 'level1',
12
+ name: 'Bronze',
13
+ level_number: 1,
14
+ points_required: 0,
15
+ },
16
+ next_level: {
17
+ id: 'level2',
18
+ name: 'Silver',
19
+ level_number: 2,
20
+ points_required: 500,
21
+ points_needed: 400,
22
+ },
23
+ metrics: {
24
+ logins: 10,
25
+ purchases: 2,
26
+ },
27
+ badges: [
28
+ {
29
+ id: 'badge1',
30
+ name: 'First Steps',
31
+ description: 'Completed first action',
32
+ icon_url: 'https://example.com/badge1.png',
33
+ earned_at: '2024-01-01T00:00:00Z',
34
+ },
35
+ ],
36
+ },
37
+
38
+ trackEventResponse: {
39
+ status: 'queued',
40
+ message: 'Event accepted for processing',
41
+ },
42
+
43
+ trackEventWithProfileResponse: {
44
+ status: 'queued',
45
+ message: 'Event accepted for processing',
46
+ profile: {
47
+ user_id: 'user123',
48
+ points: 110,
49
+ persona: 'Achiever',
50
+ level: {
51
+ id: 'level1',
52
+ name: 'Bronze',
53
+ level_number: 1,
54
+ points_required: 0,
55
+ },
56
+ next_level: {
57
+ id: 'level2',
58
+ name: 'Silver',
59
+ level_number: 2,
60
+ points_required: 500,
61
+ points_needed: 390,
62
+ },
63
+ metrics: {},
64
+ badges: [],
65
+ },
66
+ },
67
+
68
+ leaderboardResponse: {
69
+ timeframe: 'all-time',
70
+ page: 1,
71
+ limit: 50,
72
+ total: 100,
73
+ rankings: [
74
+ {
75
+ rank: 1,
76
+ user_id: 'user1',
77
+ points: 1000,
78
+ level: {
79
+ id: 'level3',
80
+ name: 'Gold',
81
+ level_number: 3,
82
+ },
83
+ },
84
+ {
85
+ rank: 2,
86
+ user_id: 'user2',
87
+ points: 900,
88
+ level: {
89
+ id: 'level2',
90
+ name: 'Silver',
91
+ level_number: 2,
92
+ },
93
+ },
94
+ ],
95
+ },
96
+
97
+ userRankResponse: {
98
+ user_id: 'user123',
99
+ rank: 42,
100
+ points: 850,
101
+ total_users: 1000,
102
+ },
103
+
104
+ badgesListResponse: {
105
+ badges: [
106
+ {
107
+ id: 'badge1',
108
+ name: 'First Steps',
109
+ description: 'Completed first action',
110
+ icon_url: 'https://example.com/badge1.png',
111
+ is_active: true,
112
+ unlock_criteria: {
113
+ metric: 'actions',
114
+ value: 1,
115
+ },
116
+ },
117
+ ],
118
+ pagination: {
119
+ page: 1,
120
+ limit: 50,
121
+ total: 10,
122
+ totalPages: 1,
123
+ },
124
+ },
125
+
126
+ levelsListResponse: {
127
+ levels: [
128
+ {
129
+ id: 'level1',
130
+ name: 'Bronze',
131
+ level_number: 1,
132
+ points_required: 0,
133
+ description: 'Starting level',
134
+ icon_url: 'https://example.com/bronze.png',
135
+ },
136
+ {
137
+ id: 'level2',
138
+ name: 'Silver',
139
+ level_number: 2,
140
+ points_required: 500,
141
+ description: 'Intermediate level',
142
+ icon_url: 'https://example.com/silver.png',
143
+ },
144
+ ],
145
+ pagination: {
146
+ page: 1,
147
+ limit: 50,
148
+ total: 5,
149
+ totalPages: 1,
150
+ },
151
+ },
152
+
153
+ questionnaireResponse: {
154
+ id: 'questionnaire1',
155
+ slug: 'user-persona',
156
+ title: 'User Persona Quiz',
157
+ description: 'Determine your user persona',
158
+ is_active: true,
159
+ questions: [
160
+ {
161
+ id: 'q1',
162
+ text: 'What motivates you?',
163
+ order: 1,
164
+ answer_options: [
165
+ {
166
+ id: 'a1',
167
+ text: 'Competition',
168
+ persona_weights: { Competitor: 1 },
169
+ },
170
+ {
171
+ id: 'a2',
172
+ text: 'Exploration',
173
+ persona_weights: { Explorer: 1 },
174
+ },
175
+ ],
176
+ },
177
+ ],
178
+ },
179
+
180
+ bulkUsersResponse: {
181
+ users: [
182
+ {
183
+ user_id: 'user1',
184
+ points: 100,
185
+ persona: null,
186
+ level: null,
187
+ next_level: null,
188
+ metrics: {},
189
+ badges: [],
190
+ },
191
+ {
192
+ user_id: 'user2',
193
+ points: 200,
194
+ persona: 'Achiever',
195
+ level: {
196
+ id: 'level1',
197
+ name: 'Bronze',
198
+ level_number: 1,
199
+ points_required: 0,
200
+ },
201
+ next_level: null,
202
+ metrics: {},
203
+ badges: [],
204
+ },
205
+ ],
206
+ },
207
+
208
+ ahaScoreResponse: {
209
+ success: true,
210
+ data: {
211
+ user_id: 'user123',
212
+ current_score: 75,
213
+ declarative_score: 80,
214
+ inferred_score: 70,
215
+ status: 'activated',
216
+ history: {
217
+ initial: 50,
218
+ initial_date: '2024-01-01T00:00:00Z',
219
+ previous: 70,
220
+ },
221
+ },
222
+ },
223
+
224
+ ahaDeclarationResponse: {
225
+ success: true,
226
+ message: 'Declarative score recorded. Calculation in progress.',
227
+ },
228
+
229
+ answerSubmissionResponse: {
230
+ status: 'accepted',
231
+ message: 'Answers queued for processing',
232
+ },
233
+
234
+ leaderboardsListResponse: {
235
+ leaderboards: [
236
+ {
237
+ id: 'lb1',
238
+ name: 'Top Players',
239
+ description: 'Overall top players',
240
+ type: 'POINTS',
241
+ timeframe: 'all-time',
242
+ is_active: true,
243
+ created_at: '2024-01-01T00:00:00Z',
244
+ },
245
+ ],
246
+ pagination: {
247
+ page: 1,
248
+ limit: 50,
249
+ total: 5,
250
+ totalPages: 1,
251
+ },
252
+ },
253
+
254
+ customLeaderboardResponse: {
255
+ leaderboard: {
256
+ id: 'lb1',
257
+ name: 'Top Players',
258
+ description: 'Overall top players',
259
+ type: 'POINTS',
260
+ timeframe: 'all-time',
261
+ is_active: true,
262
+ created_at: '2024-01-01T00:00:00Z',
263
+ },
264
+ rankings: [
265
+ {
266
+ rank: 1,
267
+ user_id: 'user1',
268
+ points: 1000,
269
+ level: null,
270
+ },
271
+ ],
272
+ pagination: {
273
+ page: 1,
274
+ limit: 50,
275
+ total: 10,
276
+ totalPages: 1,
277
+ },
278
+ },
279
+ };
280
+
281
+ export const mockErrors = {
282
+ validationError: {
283
+ error: 'Validation failed',
284
+ details: [
285
+ {
286
+ field: 'user_id',
287
+ message: 'User ID is required',
288
+ },
289
+ ],
290
+ },
291
+
292
+ notFoundError: {
293
+ error: 'User not found',
294
+ message: "User 'user123' does not exist in this project",
295
+ },
296
+
297
+ serverError: {
298
+ error: 'Internal server error',
299
+ },
300
+
301
+ queueFullError: {
302
+ error: 'Service unavailable',
303
+ message: 'Event queue is full. Please retry later.',
304
+ },
305
+
306
+ unauthorizedError: {
307
+ error: 'Unauthorized',
308
+ message: 'Invalid or missing API key',
309
+ },
310
+
311
+ invalidTimeframeError: {
312
+ error: 'Invalid timeframe',
313
+ message: "Timeframe must be one of: all-time, weekly, monthly",
314
+ },
315
+
316
+ invalidPaginationError: {
317
+ error: 'Invalid limit parameter',
318
+ message: 'Limit must be between 1 and 100',
319
+ },
320
+
321
+ ahaValueError: {
322
+ error: 'Validation failed',
323
+ details: [
324
+ {
325
+ field: 'value',
326
+ message: 'value must be an integer between 1 and 5',
327
+ },
328
+ ],
329
+ },
330
+ };