@mobtechi/sdk-core 1.1.3 → 1.1.5

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 CHANGED
@@ -22,7 +22,6 @@ import { MobTechi, MemoryStorageProvider } from '@mobtechi/sdk-core';
22
22
  const mobtechi = new MobTechi({
23
23
  baseUrl: 'https://api.mobtechi.com', // Replace with your environment URL
24
24
  appId: 'your-app-id',
25
- orgId: 'your-org-id', // Optional
26
25
  storage: new MemoryStorageProvider(), // Replace with localStorage/AsyncStorage provider for persistence
27
26
  tokenKey: 'mobtechi_auth_token'
28
27
  });
@@ -40,13 +39,13 @@ Handles user identity, login, OTP verification, and token management automatical
40
39
 
41
40
  ```typescript
42
41
  // 1. Identify User
43
- await mobtechi.api.identify({ identifier: 'user@example.com', app_id: '...', org_id: '...' });
42
+ await mobtechi.api.identify({ identifier: 'user@example.com', app_id: '...'});
44
43
 
45
44
  // 2. Login (Send OTP or Password)
46
- await mobtechi.login({ identifier: 'user@example.com', app_id: '...', org_id: '...' });
45
+ await mobtechi.login({ identifier: 'user@example.com', app_id: '...' });
47
46
 
48
47
  // 3. Verify OTP (Automatically sets the access token upon success)
49
- await mobtechi.verifyOtp({ identifier: 'user@example.com', code: '123456', app_id: '...', org_id: '...' });
48
+ await mobtechi.verifyOtp({ identifier: 'user@example.com', code: '123456', app_id: '...' });
50
49
 
51
50
  // 4. Logout (Clears local token and resets entitlements)
52
51
  await mobtechi.logout();
@@ -84,7 +83,7 @@ Fetch user-specific or app-specific ad configurations and claim rewards.
84
83
  const adsConfig = await mobtechi.api.getAdsConfig('your-org-id', 'your-app-key');
85
84
 
86
85
  // Claim rewards
87
- await mobtechi.api.claimReward({ orgId: '...', appKey: '...', rewardType: '...' });
86
+ await mobtechi.api.claimReward({ appKey: '...', rewardType: '...' });
88
87
  ```
89
88
 
90
89
  ## Advanced Usage
package/dist/index.d.mts CHANGED
@@ -8,7 +8,12 @@ interface AuthDto {
8
8
  interface VerifyOtpDto {
9
9
  email?: string;
10
10
  mobile?: string;
11
+ user_id?: string;
11
12
  otp: string;
13
+ revoke_others?: boolean;
14
+ guest_id?: string;
15
+ device_id?: string;
16
+ referral_code?: string;
12
17
  }
13
18
  interface MobTechiUser {
14
19
  id: string;
@@ -22,7 +27,6 @@ interface MobTechiUser {
22
27
  dob: string | null;
23
28
  preferences: Record<string, any> | null;
24
29
  full_name: string | null;
25
- org_id: string | null;
26
30
  is_premium?: boolean;
27
31
  has_ads?: boolean;
28
32
  flags?: Record<string, any>;
@@ -93,7 +97,6 @@ declare class MemoryStorageProvider implements StorageProvider {
93
97
  interface MobTechiClientOptions {
94
98
  baseUrl: string;
95
99
  appId?: string;
96
- orgId?: string;
97
100
  storage?: StorageProvider;
98
101
  tokenKey?: string;
99
102
  }
@@ -104,8 +107,7 @@ declare class MobTechiClient {
104
107
  private tokenKey;
105
108
  private currentToken;
106
109
  appId?: string;
107
- orgId?: string;
108
- constructor(optionsOrBaseUrl: MobTechiClientOptions | string, appId?: string, orgId?: string, storage?: StorageProvider, tokenKey?: string);
110
+ constructor(optionsOrBaseUrl: MobTechiClientOptions | string, appId?: string, storage?: StorageProvider, tokenKey?: string);
109
111
  setToken(token: string | null): Promise<void>;
110
112
  getToken(): Promise<string | null>;
111
113
  get<T = any>(path: string, config?: any): Promise<{
@@ -130,11 +132,9 @@ declare class MobTechiClient {
130
132
  }>;
131
133
  identify(payload: AuthDto & {
132
134
  app_id: string;
133
- org_id: string;
134
135
  }): Promise<any>;
135
136
  login(payload: AuthDto & {
136
137
  app_id: string;
137
- org_id: string;
138
138
  }): Promise<{
139
139
  status: number;
140
140
  data: any;
@@ -142,7 +142,6 @@ declare class MobTechiClient {
142
142
  register(payload: any): Promise<any>;
143
143
  verifyOtp(payload: VerifyOtpDto & {
144
144
  app_id: string;
145
- org_id: string;
146
145
  }): Promise<{
147
146
  status: number;
148
147
  data: any;
@@ -150,13 +149,13 @@ declare class MobTechiClient {
150
149
  validateToken(token: string): Promise<any>;
151
150
  refreshToken(refresh_token: string): Promise<any>;
152
151
  logout(token: string): Promise<void>;
152
+ logoutAll(token: string): Promise<void>;
153
153
  updateProfile(payload: any, token?: string): Promise<any>;
154
- getPlans(orgIdOrAppKey?: string, appKeyOrUserId?: string, token?: string): Promise<any>;
154
+ getPlans(appKey?: string, token?: string): Promise<any>;
155
155
  getSubscriptionStatus(appKey: string, token?: string): Promise<any>;
156
156
  getEntitlements(appKey: string): Promise<any>;
157
157
  createOrder(payload: {
158
158
  planId: string;
159
- orgId: string;
160
159
  appKey: string;
161
160
  }, token?: string): Promise<any>;
162
161
  createSubscription(appKey: string, payload: {
@@ -165,9 +164,8 @@ declare class MobTechiClient {
165
164
  createTopUp(appKey: string, payload: {
166
165
  credits: number;
167
166
  }): Promise<any>;
168
- getAdsConfig(orgId: string, appKey: string): Promise<any>;
167
+ getAdsConfig(appKey: string): Promise<any>;
169
168
  claimReward(payload: {
170
- orgId: string;
171
169
  appKey: string;
172
170
  rewardType: string;
173
171
  }): Promise<any>;
@@ -223,29 +221,50 @@ declare class AnalyticsTracker {
223
221
  private capture;
224
222
  }
225
223
 
224
+ declare class PresenceTracker {
225
+ private readonly api;
226
+ private readonly appId;
227
+ private timerId;
228
+ private currentSessionId;
229
+ private activeScreenGetter;
230
+ private intervalMs;
231
+ private visibilityHandler;
232
+ constructor(api: MobTechiClient, appId: string);
233
+ /**
234
+ * Starts periodic heartbeat to track user online presence across ecosystem applications.
235
+ * Automatically pauses heartbeat when app/tab is backgrounded.
236
+ */
237
+ startHeartbeat(sessionId: string, activeScreenProvider?: () => string, intervalSeconds?: number): void;
238
+ /**
239
+ * Stops active heartbeat timer and cleans up lifecycle listeners.
240
+ */
241
+ stopHeartbeat(): void;
242
+ private sendHeartbeat;
243
+ }
244
+
226
245
  declare class MobTechi {
227
246
  api: MobTechiClient;
228
247
  entitlements: EntitlementEngine;
229
248
  analytics: AnalyticsTracker;
249
+ presence: PresenceTracker;
230
250
  constructor(options: MobTechiClientOptions);
231
251
  /**
232
252
  * Auth helpers to standardize the flow and auto-configure internal state
233
253
  */
234
254
  login(payload: AuthDto & {
235
255
  app_id: string;
236
- org_id: string;
237
256
  }): Promise<{
238
257
  status: number;
239
258
  data: any;
240
259
  }>;
241
260
  verifyOtp(payload: VerifyOtpDto & {
242
261
  app_id: string;
243
- org_id: string;
244
262
  }): Promise<{
245
263
  status: number;
246
264
  data: any;
247
265
  }>;
248
266
  logout(): Promise<void>;
267
+ logoutAll(): Promise<void>;
249
268
  /**
250
269
  * Synchronize the current user's subscription and entitlements state
251
270
  */
@@ -261,4 +280,4 @@ declare const MobTechiSDK: {
261
280
  isPremiumUser: () => boolean;
262
281
  };
263
282
 
264
- export { type AnalyticsEventPayload, AnalyticsTracker, type AuthDto, type AuthState, type Entitlement, EntitlementEngine, MemoryStorageProvider, MobTechi, MobTechiClient, type MobTechiClientOptions, type MobTechiEvent, MobTechiSDK, type MobTechiUser, type PlanComparison, type StorageProvider, type SubscriptionEnvironment, type SubscriptionPlan, type SubscriptionTopup, type VerifyOtpDto };
283
+ export { type AnalyticsEventPayload, AnalyticsTracker, type AuthDto, type AuthState, type Entitlement, EntitlementEngine, MemoryStorageProvider, MobTechi, MobTechiClient, type MobTechiClientOptions, type MobTechiEvent, MobTechiSDK, type MobTechiUser, type PlanComparison, PresenceTracker, type StorageProvider, type SubscriptionEnvironment, type SubscriptionPlan, type SubscriptionTopup, type VerifyOtpDto };
package/dist/index.d.ts CHANGED
@@ -8,7 +8,12 @@ interface AuthDto {
8
8
  interface VerifyOtpDto {
9
9
  email?: string;
10
10
  mobile?: string;
11
+ user_id?: string;
11
12
  otp: string;
13
+ revoke_others?: boolean;
14
+ guest_id?: string;
15
+ device_id?: string;
16
+ referral_code?: string;
12
17
  }
13
18
  interface MobTechiUser {
14
19
  id: string;
@@ -22,7 +27,6 @@ interface MobTechiUser {
22
27
  dob: string | null;
23
28
  preferences: Record<string, any> | null;
24
29
  full_name: string | null;
25
- org_id: string | null;
26
30
  is_premium?: boolean;
27
31
  has_ads?: boolean;
28
32
  flags?: Record<string, any>;
@@ -93,7 +97,6 @@ declare class MemoryStorageProvider implements StorageProvider {
93
97
  interface MobTechiClientOptions {
94
98
  baseUrl: string;
95
99
  appId?: string;
96
- orgId?: string;
97
100
  storage?: StorageProvider;
98
101
  tokenKey?: string;
99
102
  }
@@ -104,8 +107,7 @@ declare class MobTechiClient {
104
107
  private tokenKey;
105
108
  private currentToken;
106
109
  appId?: string;
107
- orgId?: string;
108
- constructor(optionsOrBaseUrl: MobTechiClientOptions | string, appId?: string, orgId?: string, storage?: StorageProvider, tokenKey?: string);
110
+ constructor(optionsOrBaseUrl: MobTechiClientOptions | string, appId?: string, storage?: StorageProvider, tokenKey?: string);
109
111
  setToken(token: string | null): Promise<void>;
110
112
  getToken(): Promise<string | null>;
111
113
  get<T = any>(path: string, config?: any): Promise<{
@@ -130,11 +132,9 @@ declare class MobTechiClient {
130
132
  }>;
131
133
  identify(payload: AuthDto & {
132
134
  app_id: string;
133
- org_id: string;
134
135
  }): Promise<any>;
135
136
  login(payload: AuthDto & {
136
137
  app_id: string;
137
- org_id: string;
138
138
  }): Promise<{
139
139
  status: number;
140
140
  data: any;
@@ -142,7 +142,6 @@ declare class MobTechiClient {
142
142
  register(payload: any): Promise<any>;
143
143
  verifyOtp(payload: VerifyOtpDto & {
144
144
  app_id: string;
145
- org_id: string;
146
145
  }): Promise<{
147
146
  status: number;
148
147
  data: any;
@@ -150,13 +149,13 @@ declare class MobTechiClient {
150
149
  validateToken(token: string): Promise<any>;
151
150
  refreshToken(refresh_token: string): Promise<any>;
152
151
  logout(token: string): Promise<void>;
152
+ logoutAll(token: string): Promise<void>;
153
153
  updateProfile(payload: any, token?: string): Promise<any>;
154
- getPlans(orgIdOrAppKey?: string, appKeyOrUserId?: string, token?: string): Promise<any>;
154
+ getPlans(appKey?: string, token?: string): Promise<any>;
155
155
  getSubscriptionStatus(appKey: string, token?: string): Promise<any>;
156
156
  getEntitlements(appKey: string): Promise<any>;
157
157
  createOrder(payload: {
158
158
  planId: string;
159
- orgId: string;
160
159
  appKey: string;
161
160
  }, token?: string): Promise<any>;
162
161
  createSubscription(appKey: string, payload: {
@@ -165,9 +164,8 @@ declare class MobTechiClient {
165
164
  createTopUp(appKey: string, payload: {
166
165
  credits: number;
167
166
  }): Promise<any>;
168
- getAdsConfig(orgId: string, appKey: string): Promise<any>;
167
+ getAdsConfig(appKey: string): Promise<any>;
169
168
  claimReward(payload: {
170
- orgId: string;
171
169
  appKey: string;
172
170
  rewardType: string;
173
171
  }): Promise<any>;
@@ -223,29 +221,50 @@ declare class AnalyticsTracker {
223
221
  private capture;
224
222
  }
225
223
 
224
+ declare class PresenceTracker {
225
+ private readonly api;
226
+ private readonly appId;
227
+ private timerId;
228
+ private currentSessionId;
229
+ private activeScreenGetter;
230
+ private intervalMs;
231
+ private visibilityHandler;
232
+ constructor(api: MobTechiClient, appId: string);
233
+ /**
234
+ * Starts periodic heartbeat to track user online presence across ecosystem applications.
235
+ * Automatically pauses heartbeat when app/tab is backgrounded.
236
+ */
237
+ startHeartbeat(sessionId: string, activeScreenProvider?: () => string, intervalSeconds?: number): void;
238
+ /**
239
+ * Stops active heartbeat timer and cleans up lifecycle listeners.
240
+ */
241
+ stopHeartbeat(): void;
242
+ private sendHeartbeat;
243
+ }
244
+
226
245
  declare class MobTechi {
227
246
  api: MobTechiClient;
228
247
  entitlements: EntitlementEngine;
229
248
  analytics: AnalyticsTracker;
249
+ presence: PresenceTracker;
230
250
  constructor(options: MobTechiClientOptions);
231
251
  /**
232
252
  * Auth helpers to standardize the flow and auto-configure internal state
233
253
  */
234
254
  login(payload: AuthDto & {
235
255
  app_id: string;
236
- org_id: string;
237
256
  }): Promise<{
238
257
  status: number;
239
258
  data: any;
240
259
  }>;
241
260
  verifyOtp(payload: VerifyOtpDto & {
242
261
  app_id: string;
243
- org_id: string;
244
262
  }): Promise<{
245
263
  status: number;
246
264
  data: any;
247
265
  }>;
248
266
  logout(): Promise<void>;
267
+ logoutAll(): Promise<void>;
249
268
  /**
250
269
  * Synchronize the current user's subscription and entitlements state
251
270
  */
@@ -261,4 +280,4 @@ declare const MobTechiSDK: {
261
280
  isPremiumUser: () => boolean;
262
281
  };
263
282
 
264
- export { type AnalyticsEventPayload, AnalyticsTracker, type AuthDto, type AuthState, type Entitlement, EntitlementEngine, MemoryStorageProvider, MobTechi, MobTechiClient, type MobTechiClientOptions, type MobTechiEvent, MobTechiSDK, type MobTechiUser, type PlanComparison, type StorageProvider, type SubscriptionEnvironment, type SubscriptionPlan, type SubscriptionTopup, type VerifyOtpDto };
283
+ export { type AnalyticsEventPayload, AnalyticsTracker, type AuthDto, type AuthState, type Entitlement, EntitlementEngine, MemoryStorageProvider, MobTechi, MobTechiClient, type MobTechiClientOptions, type MobTechiEvent, MobTechiSDK, type MobTechiUser, type PlanComparison, PresenceTracker, type StorageProvider, type SubscriptionEnvironment, type SubscriptionPlan, type SubscriptionTopup, type VerifyOtpDto };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var v=Object.create;var w=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var x=Object.getPrototypeOf,$=Object.prototype.hasOwnProperty;var A=(r,t)=>{for(var e in t)w(r,e,{get:t[e],enumerable:!0})},_=(r,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of P(t))!$.call(r,i)&&i!==e&&w(r,i,{get:()=>t[i],enumerable:!(s=I(t,i))||s.enumerable});return r};var E=(r,t,e)=>(e=r!=null?v(x(r)):{},_(t||!r||!r.__esModule?w(e,"default",{value:r,enumerable:!0}):e,r)),S=r=>_(w({},"__esModule",{value:!0}),r);var D={};A(D,{AnalyticsTracker:()=>h,EntitlementEngine:()=>g,MemoryStorageProvider:()=>d,MobTechi:()=>T,MobTechiClient:()=>p,MobTechiSDK:()=>b});module.exports=S(D);var d=class{store={};getItem(t){return this.store[t]||null}setItem(t,e){this.store[t]=e}removeItem(t){delete this.store[t]}};var k=E(require("axios"));var p=class{client;baseUrl;storage;tokenKey;currentToken=null;appId;orgId;constructor(t,e,s,i,u){let m,o=e,l=s,a=i,y=u;typeof t=="object"&&t!==null?(m=t.baseUrl,o=t.appId,l=t.orgId,a=t.storage,y=t.tokenKey):m=t,this.baseUrl=m,this.appId=o,this.orgId=l,this.storage=a||new d,this.tokenKey=y||"mobtechi_auth_token",this.client=k.default.create({baseURL:this.baseUrl,headers:{"Content-Type":"application/json",...o&&{"x-app-id":o},...l&&{"x-org-id":l}}}),this.client.interceptors.request.use(async n=>{if(!this.currentToken){let c=await this.storage.getItem(this.tokenKey);c&&(this.currentToken=c)}return this.currentToken&&n.headers&&(n.headers.Authorization=`Bearer ${this.currentToken}`),this.currentToken==="guest_access_token"&&(n.adapter=async()=>{let c=n.url||"",f={};return c.includes("/subscription/status")?f={isPremium:!0,tier:"premium",expiresAt:new Date(Date.now()+31536e6).toISOString()}:c.includes("/subscription/plans")?f=[{id:"plan_1",name:"Premium Guest",price:0}]:c.includes("/ads/config")&&(f={enabled:!1}),{data:f,status:200,statusText:"OK",headers:{},config:n,request:{}}}),n})}async setToken(t){this.currentToken=t,t?await this.storage.setItem(this.tokenKey,t):await this.storage.removeItem(this.tokenKey)}async getToken(){return this.currentToken||(this.currentToken=await this.storage.getItem(this.tokenKey)),this.currentToken}async get(t,e){let s=await this.client.get(t,e);return{data:s.data,status:s.status}}async post(t,e,s){let i=await this.client.post(t,e,s);return{data:i.data,status:i.status}}async put(t,e,s){let i=await this.client.put(t,e,s);return{data:i.data,status:i.status}}async patch(t,e,s){let i=await this.client.patch(t,e,s);return{data:i.data,status:i.status}}async delete(t,e){let s=await this.client.delete(t,e);return{data:s.data,status:s.status}}async identify(t){return(await this.client.post("/auth/identify",t)).data}async login(t){let e=await this.client.post("/auth/login",t);return{status:e.status,data:e.data}}async register(t){return(await this.client.post("/auth/register",t)).data}async verifyOtp(t){let e=await this.client.post("/auth/otp_verify",t);return{status:e.status,data:e.data}}async validateToken(t){return(await this.client.get("/auth/validate",{headers:{Authorization:`Bearer ${t}`}})).data}async refreshToken(t){return(await this.client.post("/auth/refresh",{refresh_token:t})).data}async logout(t){await this.client.delete("/auth/logout",{headers:{Authorization:`Bearer ${t}`}})}async updateProfile(t,e){let s=e?{Authorization:`Bearer ${e}`}:{};return(await this.client.put("/user/update-profile",t,{headers:s})).data}async getPlans(t,e,s){let i=t||this.orgId||"",u=e||this.appId||"";if(t&&!(a=>a?/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(a):!1)(t)){u=t;let a=e||"",y=a?`?user_id=${a}`:"";return(await this.client.get(`/${u}/subscriptions/plans${y}`)).data}let o=s?{headers:{Authorization:`Bearer ${s}`}}:{};return(await this.client.get(`/user/subscription/plans?orgId=${i}&appKey=${u}`,o)).data}async getSubscriptionStatus(t,e){return(await this.client.get(`/user/subscription/status?appId=${t}`)).data}async getEntitlements(t){return(await this.client.get(`/${t}/subscriptions/entitlements`)).data}async createOrder(t,e){return(await this.client.post("/user/subscription/create-order",t)).data}async createSubscription(t,e){return(await this.client.post(`/${t}/subscriptions/create`,e)).data}async createTopUp(t,e){return(await this.client.post(`/${t}/subscriptions/topup`,e)).data}async getAdsConfig(t,e){return(await this.client.get(`/user/ads/config?orgId=${t}&appKey=${e}`)).data}async claimReward(t){return(await this.client.post("/user/rewards/ad-claim",t)).data}};var g=class{entitlements=[];isPremiumFlag=!1;setState(t,e=[]){this.isPremiumFlag=t,this.entitlements=e}checkPremium(){return this.isPremiumFlag?!0:this.entitlements.some(t=>t.active&&t.tier!=="FREE")}hasFeature(t){return this.isPremiumFlag?!0:this.entitlements.some(e=>e.active&&e.granted_features&&e.granted_features.includes(t))}};var h=class{constructor(t,e){this.api=t;this.appId=e}api;appId;sessionEvents=new Set;async shouldDeduplicate(t){if(t==="onboarding_completed"||t==="first_code_execution"){let e=`mt_dedup_${t}`;return await this.api.storage.getItem(e)==="true"}return t==="paywall_viewed"?this.sessionEvents.has(t):!1}async markTracked(t){if(t==="onboarding_completed"||t==="first_code_execution"){let e=`mt_dedup_${t}`;await this.api.storage.setItem(e,"true")}else t==="paywall_viewed"&&this.sessionEvents.add(t)}track(t,e){(async()=>{await this.shouldDeduplicate(t)||(await this.markTracked(t),await this.capture({event_name:t,payload:e}))})().catch(s=>{console.warn(`[MobTechi Analytics] failed async track for ${t}`,s)})}trackError(t,e,s){(async()=>{await this.capture({event_name:t,is_error:!0,stack_trace:e.stack,metadata:s})})().catch(i=>{console.warn(`[MobTechi Analytics] failed async trackError for ${t}`,i)})}async capture(t){try{await this.api.post("/analytics/capture",{app_id:this.appId,...t})}catch{console.warn(`[MobTechi Analytics] Failed to track event: ${t.event_name}`)}}};var T=class{api;entitlements;analytics;constructor(t){this.api=new p(t),this.entitlements=new g,this.analytics=new h(this.api,t.appId||"unknown")}async login(t){return await this.api.login(t)}async verifyOtp(t){let e=await this.api.verifyOtp(t);return e.data&&e.data.token&&await this.api.setToken(e.data.token),e}async logout(){let t=await this.api.getToken();if(t)try{await this.api.logout(t)}catch{}await this.api.setToken(null),this.entitlements.setState(!1,[])}async syncEntitlements(t){try{let e=await this.api.getEntitlements(t);if(e){let s=!!e.isPremium,i=e.features||[];this.entitlements.setState(s,i),b.setPremiumUser(s)}return e}catch(e){throw console.error("Failed to sync entitlements",e),e}}},b={_isPremium:!1,setPremiumUser:r=>{b._isPremium=r},isPremiumUser:()=>b._isPremium};0&&(module.exports={AnalyticsTracker,EntitlementEngine,MemoryStorageProvider,MobTechi,MobTechiClient,MobTechiSDK});
1
+ "use strict";var k=Object.create;var g=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var P=Object.getPrototypeOf,S=Object.prototype.hasOwnProperty;var x=(r,t)=>{for(var e in t)g(r,e,{get:t[e],enumerable:!0})},w=(r,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of _(t))!S.call(r,s)&&s!==e&&g(r,s,{get:()=>t[s],enumerable:!(i=I(t,s))||i.enumerable});return r};var A=(r,t,e)=>(e=r!=null?k(P(r)):{},w(t||!r||!r.__esModule?g(e,"default",{value:r,enumerable:!0}):e,r)),E=r=>w(g({},"__esModule",{value:!0}),r);var M={};x(M,{AnalyticsTracker:()=>u,EntitlementEngine:()=>l,MemoryStorageProvider:()=>o,MobTechi:()=>f,MobTechiClient:()=>c,MobTechiSDK:()=>y,PresenceTracker:()=>d});module.exports=E(M);var o=class{store={};getItem(t){return this.store[t]||null}setItem(t,e){this.store[t]=e}removeItem(t){delete this.store[t]}};var T=A(require("axios"));var c=class{client;baseUrl;storage;tokenKey;currentToken=null;appId;constructor(t,e,i,s){let p,h=e,v=i,b=s;typeof t=="object"&&t!==null?(p=t.baseUrl,h=t.appId,v=t.storage,b=t.tokenKey):p=t,this.baseUrl=p,this.appId=h,this.storage=v||new o,this.tokenKey=b||"mobtechi_auth_token",this.client=T.default.create({baseURL:this.baseUrl,headers:{"Content-Type":"application/json",...h&&{"x-app-id":h}}}),this.client.interceptors.request.use(async a=>{if(!this.currentToken){let n=await this.storage.getItem(this.tokenKey);n&&(this.currentToken=n)}return this.currentToken&&a.headers&&(a.headers.Authorization=`Bearer ${this.currentToken}`),this.currentToken==="guest_access_token"&&(a.adapter=async()=>{let n=a.url||"",m={};return n.includes("/subscription/status")?m={isPremium:!0,tier:"premium",expiresAt:new Date(Date.now()+31536e6).toISOString()}:n.includes("/subscription/plans")?m=[{id:"plan_1",name:"Premium Guest",price:0}]:n.includes("/ads/config")&&(m={enabled:!1}),{data:m,status:200,statusText:"OK",headers:{},config:a,request:{}}}),a})}async setToken(t){this.currentToken=t,t?await this.storage.setItem(this.tokenKey,t):await this.storage.removeItem(this.tokenKey)}async getToken(){return this.currentToken||(this.currentToken=await this.storage.getItem(this.tokenKey)),this.currentToken}async get(t,e){let i=await this.client.get(t,e);return{data:i.data,status:i.status}}async post(t,e,i){let s=await this.client.post(t,e,i);return{data:s.data,status:s.status}}async put(t,e,i){let s=await this.client.put(t,e,i);return{data:s.data,status:s.status}}async patch(t,e,i){let s=await this.client.patch(t,e,i);return{data:s.data,status:s.status}}async delete(t,e){let i=await this.client.delete(t,e);return{data:i.data,status:i.status}}async identify(t){return(await this.client.post("/auth/identify",t)).data}async login(t){let e=await this.client.post("/auth/login",t);return{status:e.status,data:e.data}}async register(t){return(await this.client.post("/auth/register",t)).data}async verifyOtp(t){let e=await this.client.post("/auth/otp_verify",t);return{status:e.status,data:e.data}}async validateToken(t){return(await this.client.get("/auth/validate",{headers:{Authorization:`Bearer ${t}`}})).data}async refreshToken(t){return(await this.client.post("/auth/refresh",{refresh_token:t})).data}async logout(t){await this.client.delete("/auth/logout",{headers:{Authorization:`Bearer ${t}`}})}async logoutAll(t){await this.client.delete("/auth/logout_all",{headers:{Authorization:`Bearer ${t}`}})}async updateProfile(t,e){let i=e?{Authorization:`Bearer ${e}`}:{};return(await this.client.put("/user/update-profile",t,{headers:i})).data}async getPlans(t,e){let i=t||this.appId||"",s=e?{headers:{Authorization:`Bearer ${e}`}}:{};return(await this.client.get(`/user/subscription/plans?appKey=${i}`,s)).data}async getSubscriptionStatus(t,e){return(await this.client.get(`/user/subscription/status?appId=${t}`)).data}async getEntitlements(t){return(await this.client.get(`/${t}/subscriptions/entitlements`)).data}async createOrder(t,e){return(await this.client.post("/user/subscription/create-order",t)).data}async createSubscription(t,e){return(await this.client.post(`/${t}/subscriptions/create`,e)).data}async createTopUp(t,e){return(await this.client.post(`/${t}/subscriptions/topup`,e)).data}async getAdsConfig(t){return(await this.client.get(`/user/ads/config?appKey=${t}`)).data}async claimReward(t){return(await this.client.post("/user/rewards/ad-claim",t)).data}};var l=class{entitlements=[];isPremiumFlag=!1;setState(t,e=[]){this.isPremiumFlag=t,this.entitlements=e}checkPremium(){return this.isPremiumFlag?!0:this.entitlements.some(t=>t.active&&t.tier!=="FREE")}hasFeature(t){return this.isPremiumFlag?!0:this.entitlements.some(e=>e.active&&e.granted_features&&e.granted_features.includes(t))}};var u=class{constructor(t,e){this.api=t;this.appId=e}api;appId;sessionEvents=new Set;async shouldDeduplicate(t){if(t==="onboarding_completed"||t==="first_code_execution"){let e=`mt_dedup_${t}`;return await this.api.storage.getItem(e)==="true"}return t==="paywall_viewed"?this.sessionEvents.has(t):!1}async markTracked(t){if(t==="onboarding_completed"||t==="first_code_execution"){let e=`mt_dedup_${t}`;await this.api.storage.setItem(e,"true")}else t==="paywall_viewed"&&this.sessionEvents.add(t)}track(t,e){(async()=>{await this.shouldDeduplicate(t)||(await this.markTracked(t),await this.capture({event_name:t,payload:e}))})().catch(i=>{console.warn(`[MobTechi Analytics] failed async track for ${t}`,i)})}trackError(t,e,i){(async()=>{await this.capture({event_name:t,is_error:!0,stack_trace:e.stack,metadata:i})})().catch(s=>{console.warn(`[MobTechi Analytics] failed async trackError for ${t}`,s)})}async capture(t){try{await this.api.post("/analytics/capture",{app_id:this.appId,...t})}catch{console.warn(`[MobTechi Analytics] Failed to track event: ${t.event_name}`)}}};var d=class{constructor(t,e){this.api=t;this.appId=e}api;appId;timerId=null;currentSessionId=null;activeScreenGetter=null;intervalMs=3e4;visibilityHandler=null;startHeartbeat(t,e=()=>"main",i=30){this.stopHeartbeat(),this.currentSessionId=t,this.activeScreenGetter=e,this.intervalMs=Math.max(10,i)*1e3,this.sendHeartbeat(),this.timerId=setInterval(()=>{this.sendHeartbeat()},this.intervalMs),typeof document<"u"&&typeof document.addEventListener=="function"&&(this.visibilityHandler=()=>{document.hidden?this.timerId&&(clearInterval(this.timerId),this.timerId=null):!this.timerId&&this.currentSessionId&&(this.sendHeartbeat(),this.timerId=setInterval(()=>{this.sendHeartbeat()},this.intervalMs))},document.addEventListener("visibilitychange",this.visibilityHandler))}stopHeartbeat(){this.timerId&&(clearInterval(this.timerId),this.timerId=null),typeof document<"u"&&typeof document.removeEventListener=="function"&&this.visibilityHandler&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.currentSessionId=null,this.activeScreenGetter=null}sendHeartbeat(){if(!this.currentSessionId)return;let t=this.activeScreenGetter?this.activeScreenGetter():"main";this.api.post("/analytics/presence/heartbeat",{sessionId:this.currentSessionId,activeScreen:t}).catch(e=>{})}};var f=class{api;entitlements;analytics;presence;constructor(t){this.api=new c(t),this.entitlements=new l,this.analytics=new u(this.api,t.appId||"unknown"),this.presence=new d(this.api,t.appId||"unknown")}async login(t){return await this.api.login(t)}async verifyOtp(t){let e=await this.api.verifyOtp(t);return e.data&&e.data.token&&await this.api.setToken(e.data.token),e}async logout(){let t=await this.api.getToken();if(t)try{await this.api.logout(t)}catch{}await this.api.setToken(null),this.entitlements.setState(!1,[])}async logoutAll(){let t=await this.api.getToken();if(t)try{await this.api.logoutAll(t)}catch{}await this.api.setToken(null),this.entitlements.setState(!1,[])}async syncEntitlements(t){try{let e=await this.api.getEntitlements(t);if(e){let i=!!e.isPremium,s=e.features||[];this.entitlements.setState(i,s),y.setPremiumUser(i)}return e}catch(e){throw console.error("Failed to sync entitlements",e),e}}},y={_isPremium:!1,setPremiumUser:r=>{y._isPremium=r},isPremiumUser:()=>y._isPremium};0&&(module.exports={AnalyticsTracker,EntitlementEngine,MemoryStorageProvider,MobTechi,MobTechiClient,MobTechiSDK,PresenceTracker});
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var h=class{store={};getItem(t){return this.store[t]||null}setItem(t,e){this.store[t]=e}removeItem(t){delete this.store[t]}};import T from"axios";var m=class{client;baseUrl;storage;tokenKey;currentToken=null;appId;orgId;constructor(t,e,s,i,u){let d,o=e,l=s,r=i,p=u;typeof t=="object"&&t!==null?(d=t.baseUrl,o=t.appId,l=t.orgId,r=t.storage,p=t.tokenKey):d=t,this.baseUrl=d,this.appId=o,this.orgId=l,this.storage=r||new h,this.tokenKey=p||"mobtechi_auth_token",this.client=T.create({baseURL:this.baseUrl,headers:{"Content-Type":"application/json",...o&&{"x-app-id":o},...l&&{"x-org-id":l}}}),this.client.interceptors.request.use(async a=>{if(!this.currentToken){let c=await this.storage.getItem(this.tokenKey);c&&(this.currentToken=c)}return this.currentToken&&a.headers&&(a.headers.Authorization=`Bearer ${this.currentToken}`),this.currentToken==="guest_access_token"&&(a.adapter=async()=>{let c=a.url||"",g={};return c.includes("/subscription/status")?g={isPremium:!0,tier:"premium",expiresAt:new Date(Date.now()+31536e6).toISOString()}:c.includes("/subscription/plans")?g=[{id:"plan_1",name:"Premium Guest",price:0}]:c.includes("/ads/config")&&(g={enabled:!1}),{data:g,status:200,statusText:"OK",headers:{},config:a,request:{}}}),a})}async setToken(t){this.currentToken=t,t?await this.storage.setItem(this.tokenKey,t):await this.storage.removeItem(this.tokenKey)}async getToken(){return this.currentToken||(this.currentToken=await this.storage.getItem(this.tokenKey)),this.currentToken}async get(t,e){let s=await this.client.get(t,e);return{data:s.data,status:s.status}}async post(t,e,s){let i=await this.client.post(t,e,s);return{data:i.data,status:i.status}}async put(t,e,s){let i=await this.client.put(t,e,s);return{data:i.data,status:i.status}}async patch(t,e,s){let i=await this.client.patch(t,e,s);return{data:i.data,status:i.status}}async delete(t,e){let s=await this.client.delete(t,e);return{data:s.data,status:s.status}}async identify(t){return(await this.client.post("/auth/identify",t)).data}async login(t){let e=await this.client.post("/auth/login",t);return{status:e.status,data:e.data}}async register(t){return(await this.client.post("/auth/register",t)).data}async verifyOtp(t){let e=await this.client.post("/auth/otp_verify",t);return{status:e.status,data:e.data}}async validateToken(t){return(await this.client.get("/auth/validate",{headers:{Authorization:`Bearer ${t}`}})).data}async refreshToken(t){return(await this.client.post("/auth/refresh",{refresh_token:t})).data}async logout(t){await this.client.delete("/auth/logout",{headers:{Authorization:`Bearer ${t}`}})}async updateProfile(t,e){let s=e?{Authorization:`Bearer ${e}`}:{};return(await this.client.put("/user/update-profile",t,{headers:s})).data}async getPlans(t,e,s){let i=t||this.orgId||"",u=e||this.appId||"";if(t&&!(r=>r?/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(r):!1)(t)){u=t;let r=e||"",p=r?`?user_id=${r}`:"";return(await this.client.get(`/${u}/subscriptions/plans${p}`)).data}let o=s?{headers:{Authorization:`Bearer ${s}`}}:{};return(await this.client.get(`/user/subscription/plans?orgId=${i}&appKey=${u}`,o)).data}async getSubscriptionStatus(t,e){return(await this.client.get(`/user/subscription/status?appId=${t}`)).data}async getEntitlements(t){return(await this.client.get(`/${t}/subscriptions/entitlements`)).data}async createOrder(t,e){return(await this.client.post("/user/subscription/create-order",t)).data}async createSubscription(t,e){return(await this.client.post(`/${t}/subscriptions/create`,e)).data}async createTopUp(t,e){return(await this.client.post(`/${t}/subscriptions/topup`,e)).data}async getAdsConfig(t,e){return(await this.client.get(`/user/ads/config?orgId=${t}&appKey=${e}`)).data}async claimReward(t){return(await this.client.post("/user/rewards/ad-claim",t)).data}};var y=class{entitlements=[];isPremiumFlag=!1;setState(t,e=[]){this.isPremiumFlag=t,this.entitlements=e}checkPremium(){return this.isPremiumFlag?!0:this.entitlements.some(t=>t.active&&t.tier!=="FREE")}hasFeature(t){return this.isPremiumFlag?!0:this.entitlements.some(e=>e.active&&e.granted_features&&e.granted_features.includes(t))}};var f=class{constructor(t,e){this.api=t;this.appId=e}api;appId;sessionEvents=new Set;async shouldDeduplicate(t){if(t==="onboarding_completed"||t==="first_code_execution"){let e=`mt_dedup_${t}`;return await this.api.storage.getItem(e)==="true"}return t==="paywall_viewed"?this.sessionEvents.has(t):!1}async markTracked(t){if(t==="onboarding_completed"||t==="first_code_execution"){let e=`mt_dedup_${t}`;await this.api.storage.setItem(e,"true")}else t==="paywall_viewed"&&this.sessionEvents.add(t)}track(t,e){(async()=>{await this.shouldDeduplicate(t)||(await this.markTracked(t),await this.capture({event_name:t,payload:e}))})().catch(s=>{console.warn(`[MobTechi Analytics] failed async track for ${t}`,s)})}trackError(t,e,s){(async()=>{await this.capture({event_name:t,is_error:!0,stack_trace:e.stack,metadata:s})})().catch(i=>{console.warn(`[MobTechi Analytics] failed async trackError for ${t}`,i)})}async capture(t){try{await this.api.post("/analytics/capture",{app_id:this.appId,...t})}catch{console.warn(`[MobTechi Analytics] Failed to track event: ${t.event_name}`)}}};var b=class{api;entitlements;analytics;constructor(t){this.api=new m(t),this.entitlements=new y,this.analytics=new f(this.api,t.appId||"unknown")}async login(t){return await this.api.login(t)}async verifyOtp(t){let e=await this.api.verifyOtp(t);return e.data&&e.data.token&&await this.api.setToken(e.data.token),e}async logout(){let t=await this.api.getToken();if(t)try{await this.api.logout(t)}catch{}await this.api.setToken(null),this.entitlements.setState(!1,[])}async syncEntitlements(t){try{let e=await this.api.getEntitlements(t);if(e){let s=!!e.isPremium,i=e.features||[];this.entitlements.setState(s,i),w.setPremiumUser(s)}return e}catch(e){throw console.error("Failed to sync entitlements",e),e}}},w={_isPremium:!1,setPremiumUser:n=>{w._isPremium=n},isPremiumUser:()=>w._isPremium};export{f as AnalyticsTracker,y as EntitlementEngine,h as MemoryStorageProvider,b as MobTechi,m as MobTechiClient,w as MobTechiSDK};
1
+ var u=class{store={};getItem(t){return this.store[t]||null}setItem(t,e){this.store[t]=e}removeItem(t){delete this.store[t]}};import b from"axios";var d=class{client;baseUrl;storage;tokenKey;currentToken=null;appId;constructor(t,e,i,s){let o,c=e,y=i,f=s;typeof t=="object"&&t!==null?(o=t.baseUrl,c=t.appId,y=t.storage,f=t.tokenKey):o=t,this.baseUrl=o,this.appId=c,this.storage=y||new u,this.tokenKey=f||"mobtechi_auth_token",this.client=b.create({baseURL:this.baseUrl,headers:{"Content-Type":"application/json",...c&&{"x-app-id":c}}}),this.client.interceptors.request.use(async a=>{if(!this.currentToken){let n=await this.storage.getItem(this.tokenKey);n&&(this.currentToken=n)}return this.currentToken&&a.headers&&(a.headers.Authorization=`Bearer ${this.currentToken}`),this.currentToken==="guest_access_token"&&(a.adapter=async()=>{let n=a.url||"",l={};return n.includes("/subscription/status")?l={isPremium:!0,tier:"premium",expiresAt:new Date(Date.now()+31536e6).toISOString()}:n.includes("/subscription/plans")?l=[{id:"plan_1",name:"Premium Guest",price:0}]:n.includes("/ads/config")&&(l={enabled:!1}),{data:l,status:200,statusText:"OK",headers:{},config:a,request:{}}}),a})}async setToken(t){this.currentToken=t,t?await this.storage.setItem(this.tokenKey,t):await this.storage.removeItem(this.tokenKey)}async getToken(){return this.currentToken||(this.currentToken=await this.storage.getItem(this.tokenKey)),this.currentToken}async get(t,e){let i=await this.client.get(t,e);return{data:i.data,status:i.status}}async post(t,e,i){let s=await this.client.post(t,e,i);return{data:s.data,status:s.status}}async put(t,e,i){let s=await this.client.put(t,e,i);return{data:s.data,status:s.status}}async patch(t,e,i){let s=await this.client.patch(t,e,i);return{data:s.data,status:s.status}}async delete(t,e){let i=await this.client.delete(t,e);return{data:i.data,status:i.status}}async identify(t){return(await this.client.post("/auth/identify",t)).data}async login(t){let e=await this.client.post("/auth/login",t);return{status:e.status,data:e.data}}async register(t){return(await this.client.post("/auth/register",t)).data}async verifyOtp(t){let e=await this.client.post("/auth/otp_verify",t);return{status:e.status,data:e.data}}async validateToken(t){return(await this.client.get("/auth/validate",{headers:{Authorization:`Bearer ${t}`}})).data}async refreshToken(t){return(await this.client.post("/auth/refresh",{refresh_token:t})).data}async logout(t){await this.client.delete("/auth/logout",{headers:{Authorization:`Bearer ${t}`}})}async logoutAll(t){await this.client.delete("/auth/logout_all",{headers:{Authorization:`Bearer ${t}`}})}async updateProfile(t,e){let i=e?{Authorization:`Bearer ${e}`}:{};return(await this.client.put("/user/update-profile",t,{headers:i})).data}async getPlans(t,e){let i=t||this.appId||"",s=e?{headers:{Authorization:`Bearer ${e}`}}:{};return(await this.client.get(`/user/subscription/plans?appKey=${i}`,s)).data}async getSubscriptionStatus(t,e){return(await this.client.get(`/user/subscription/status?appId=${t}`)).data}async getEntitlements(t){return(await this.client.get(`/${t}/subscriptions/entitlements`)).data}async createOrder(t,e){return(await this.client.post("/user/subscription/create-order",t)).data}async createSubscription(t,e){return(await this.client.post(`/${t}/subscriptions/create`,e)).data}async createTopUp(t,e){return(await this.client.post(`/${t}/subscriptions/topup`,e)).data}async getAdsConfig(t){return(await this.client.get(`/user/ads/config?appKey=${t}`)).data}async claimReward(t){return(await this.client.post("/user/rewards/ad-claim",t)).data}};var p=class{entitlements=[];isPremiumFlag=!1;setState(t,e=[]){this.isPremiumFlag=t,this.entitlements=e}checkPremium(){return this.isPremiumFlag?!0:this.entitlements.some(t=>t.active&&t.tier!=="FREE")}hasFeature(t){return this.isPremiumFlag?!0:this.entitlements.some(e=>e.active&&e.granted_features&&e.granted_features.includes(t))}};var h=class{constructor(t,e){this.api=t;this.appId=e}api;appId;sessionEvents=new Set;async shouldDeduplicate(t){if(t==="onboarding_completed"||t==="first_code_execution"){let e=`mt_dedup_${t}`;return await this.api.storage.getItem(e)==="true"}return t==="paywall_viewed"?this.sessionEvents.has(t):!1}async markTracked(t){if(t==="onboarding_completed"||t==="first_code_execution"){let e=`mt_dedup_${t}`;await this.api.storage.setItem(e,"true")}else t==="paywall_viewed"&&this.sessionEvents.add(t)}track(t,e){(async()=>{await this.shouldDeduplicate(t)||(await this.markTracked(t),await this.capture({event_name:t,payload:e}))})().catch(i=>{console.warn(`[MobTechi Analytics] failed async track for ${t}`,i)})}trackError(t,e,i){(async()=>{await this.capture({event_name:t,is_error:!0,stack_trace:e.stack,metadata:i})})().catch(s=>{console.warn(`[MobTechi Analytics] failed async trackError for ${t}`,s)})}async capture(t){try{await this.api.post("/analytics/capture",{app_id:this.appId,...t})}catch{console.warn(`[MobTechi Analytics] Failed to track event: ${t.event_name}`)}}};var m=class{constructor(t,e){this.api=t;this.appId=e}api;appId;timerId=null;currentSessionId=null;activeScreenGetter=null;intervalMs=3e4;visibilityHandler=null;startHeartbeat(t,e=()=>"main",i=30){this.stopHeartbeat(),this.currentSessionId=t,this.activeScreenGetter=e,this.intervalMs=Math.max(10,i)*1e3,this.sendHeartbeat(),this.timerId=setInterval(()=>{this.sendHeartbeat()},this.intervalMs),typeof document<"u"&&typeof document.addEventListener=="function"&&(this.visibilityHandler=()=>{document.hidden?this.timerId&&(clearInterval(this.timerId),this.timerId=null):!this.timerId&&this.currentSessionId&&(this.sendHeartbeat(),this.timerId=setInterval(()=>{this.sendHeartbeat()},this.intervalMs))},document.addEventListener("visibilitychange",this.visibilityHandler))}stopHeartbeat(){this.timerId&&(clearInterval(this.timerId),this.timerId=null),typeof document<"u"&&typeof document.removeEventListener=="function"&&this.visibilityHandler&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.currentSessionId=null,this.activeScreenGetter=null}sendHeartbeat(){if(!this.currentSessionId)return;let t=this.activeScreenGetter?this.activeScreenGetter():"main";this.api.post("/analytics/presence/heartbeat",{sessionId:this.currentSessionId,activeScreen:t}).catch(e=>{})}};var v=class{api;entitlements;analytics;presence;constructor(t){this.api=new d(t),this.entitlements=new p,this.analytics=new h(this.api,t.appId||"unknown"),this.presence=new m(this.api,t.appId||"unknown")}async login(t){return await this.api.login(t)}async verifyOtp(t){let e=await this.api.verifyOtp(t);return e.data&&e.data.token&&await this.api.setToken(e.data.token),e}async logout(){let t=await this.api.getToken();if(t)try{await this.api.logout(t)}catch{}await this.api.setToken(null),this.entitlements.setState(!1,[])}async logoutAll(){let t=await this.api.getToken();if(t)try{await this.api.logoutAll(t)}catch{}await this.api.setToken(null),this.entitlements.setState(!1,[])}async syncEntitlements(t){try{let e=await this.api.getEntitlements(t);if(e){let i=!!e.isPremium,s=e.features||[];this.entitlements.setState(i,s),g.setPremiumUser(i)}return e}catch(e){throw console.error("Failed to sync entitlements",e),e}}},g={_isPremium:!1,setPremiumUser:r=>{g._isPremium=r},isPremiumUser:()=>g._isPremium};export{h as AnalyticsTracker,p as EntitlementEngine,u as MemoryStorageProvider,v as MobTechi,d as MobTechiClient,g as MobTechiSDK,m as PresenceTracker};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobtechi/sdk-core",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Framework-agnostic core logic and API client for the MobTechi platform",
5
5
  "author": "MobTechi",
6
6
  "license": "MIT",