@scalemule/nextjs 0.0.29 → 0.0.31

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/dist/index.d.mts CHANGED
@@ -319,6 +319,30 @@ interface UsePushNotificationsReturn {
319
319
  }
320
320
  declare function usePushNotifications(options?: UsePushNotificationsOptions): UsePushNotificationsReturn;
321
321
 
322
+ interface UseShareOptions {
323
+ /** The canonical URL to share. Defaults to current page URL.
324
+ * IMPORTANT: Pass the content's canonical URL, not window.location.href,
325
+ * to avoid re-sharing someone else's referral code. */
326
+ url?: string;
327
+ /** Manual referral code override (skips fetch) */
328
+ referralCode?: string;
329
+ /** Auto-fetch referral code from /v1/referrals/me when authenticated */
330
+ autoFetchReferral?: boolean;
331
+ }
332
+ interface UseShareReturn {
333
+ /** Share URL — absolute, with ?rc= appended if referral code available */
334
+ shareUrl: string;
335
+ /** User's referral code, or null if unauthenticated/not fetched */
336
+ referralCode: string | null;
337
+ /** Copy shareUrl to clipboard. Returns true on success. */
338
+ copyLink: () => Promise<boolean>;
339
+ /** Whether link was recently copied (auto-resets after 2s) */
340
+ copied: boolean;
341
+ /** Loading state for referral code fetch */
342
+ loading: boolean;
343
+ }
344
+ declare function useShare(options?: UseShareOptions): UseShareReturn;
345
+
322
346
  /**
323
347
  * Client-side validation helpers
324
348
  *
@@ -477,4 +501,4 @@ declare function createSafeLogger(prefix: string): {
477
501
  error: (message: string, data?: unknown) => void;
478
502
  };
479
503
 
480
- export { ApiError, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, ListFilesParams, LoginResponse, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useUser, validateForm, validators };
504
+ export { ApiError, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, ListFilesParams, LoginResponse, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, type UseShareOptions, type UseShareReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
package/dist/index.d.ts CHANGED
@@ -319,6 +319,30 @@ interface UsePushNotificationsReturn {
319
319
  }
320
320
  declare function usePushNotifications(options?: UsePushNotificationsOptions): UsePushNotificationsReturn;
321
321
 
322
+ interface UseShareOptions {
323
+ /** The canonical URL to share. Defaults to current page URL.
324
+ * IMPORTANT: Pass the content's canonical URL, not window.location.href,
325
+ * to avoid re-sharing someone else's referral code. */
326
+ url?: string;
327
+ /** Manual referral code override (skips fetch) */
328
+ referralCode?: string;
329
+ /** Auto-fetch referral code from /v1/referrals/me when authenticated */
330
+ autoFetchReferral?: boolean;
331
+ }
332
+ interface UseShareReturn {
333
+ /** Share URL — absolute, with ?rc= appended if referral code available */
334
+ shareUrl: string;
335
+ /** User's referral code, or null if unauthenticated/not fetched */
336
+ referralCode: string | null;
337
+ /** Copy shareUrl to clipboard. Returns true on success. */
338
+ copyLink: () => Promise<boolean>;
339
+ /** Whether link was recently copied (auto-resets after 2s) */
340
+ copied: boolean;
341
+ /** Loading state for referral code fetch */
342
+ loading: boolean;
343
+ }
344
+ declare function useShare(options?: UseShareOptions): UseShareReturn;
345
+
322
346
  /**
323
347
  * Client-side validation helpers
324
348
  *
@@ -477,4 +501,4 @@ declare function createSafeLogger(prefix: string): {
477
501
  error: (message: string, data?: unknown) => void;
478
502
  };
479
503
 
480
- export { ApiError, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, ListFilesParams, LoginResponse, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useUser, validateForm, validators };
504
+ export { ApiError, type FeatureFlagEvaluation, type FeatureFlagEvaluation as FeatureFlagResult, ListFilesParams, LoginResponse, type PasswordValidationResult, type PhoneCountry, type PhoneValidationResult, type RealtimeEvent, type RealtimeMessage, type RealtimeStatus, ScaleMuleClient, ScaleMuleConfig, ScaleMuleProvider, type ScaleMuleProviderProps, UseAnalyticsOptions, UseAnalyticsReturn, UseAuthReturn, UseBillingReturn, UseContentReturn, type UseFeatureFlagsOptions, type UseFeatureFlagsReturn, type UseFeatureFlagsOptions as UseFlagsOptions, type UseFeatureFlagsReturn as UseFlagsReturn, type UsePushNotificationsOptions, type UsePushNotificationsReturn, type UseRealtimeOptions, type UseRealtimeReturn, type UseShareOptions, type UseShareReturn, UseUserReturn, User, type UsernameValidationResult, composePhone, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
package/dist/index.js CHANGED
@@ -3138,9 +3138,18 @@ function usePushNotifications(options = {}) {
3138
3138
  setIsSupported(supported);
3139
3139
  setPermission(manager.getPermissionState());
3140
3140
  if (supported) {
3141
+ const storedTokenId = manager.getTokenId();
3141
3142
  manager.isSubscribed().then((sub) => {
3142
- setIsSubscribed(sub);
3143
- setTokenId(manager.getTokenId());
3143
+ if (!sub && storedTokenId) {
3144
+ manager.unsubscribe().catch(() => {
3145
+ });
3146
+ setIsSubscribed(false);
3147
+ setTokenId(null);
3148
+ setPermission(manager.getPermissionState());
3149
+ } else {
3150
+ setIsSubscribed(sub);
3151
+ setTokenId(manager.getTokenId());
3152
+ }
3144
3153
  });
3145
3154
  }
3146
3155
  }, [manager]);
@@ -3150,10 +3159,19 @@ function usePushNotifications(options = {}) {
3150
3159
  if (event.data?.type === "push-received") {
3151
3160
  onNotificationRef.current?.(event.data.payload);
3152
3161
  }
3162
+ if (event.data?.type === "push-subscription-expired") {
3163
+ if (manager) {
3164
+ manager.unsubscribe().catch(() => {
3165
+ });
3166
+ }
3167
+ setIsSubscribed(false);
3168
+ setTokenId(null);
3169
+ setPermission(typeof Notification !== "undefined" ? Notification.permission : "unsupported");
3170
+ }
3153
3171
  }
3154
3172
  navigator.serviceWorker.addEventListener("message", handleMessage);
3155
3173
  return () => navigator.serviceWorker.removeEventListener("message", handleMessage);
3156
- }, []);
3174
+ }, [manager]);
3157
3175
  react.useEffect(() => {
3158
3176
  if (!manager) return;
3159
3177
  const currentUserId = user?.id || null;
@@ -3222,6 +3240,79 @@ function usePushNotifications(options = {}) {
3222
3240
  tokenId
3223
3241
  };
3224
3242
  }
3243
+ function useShare(options) {
3244
+ const { client, user } = useScaleMule();
3245
+ const [referralCode, setReferralCode] = react.useState(
3246
+ options?.referralCode ?? null
3247
+ );
3248
+ const [loading, setLoading] = react.useState(false);
3249
+ const [copied, setCopied] = react.useState(false);
3250
+ react.useEffect(() => {
3251
+ if (!user) {
3252
+ setReferralCode(options?.referralCode ?? null);
3253
+ setLoading(false);
3254
+ return;
3255
+ }
3256
+ if (!options?.autoFetchReferral) {
3257
+ setLoading(false);
3258
+ return;
3259
+ }
3260
+ if (options?.referralCode) {
3261
+ setReferralCode(options.referralCode);
3262
+ setLoading(false);
3263
+ return;
3264
+ }
3265
+ setReferralCode(null);
3266
+ let cancelled = false;
3267
+ setLoading(true);
3268
+ client.get("/v1/referrals/me").then((data) => {
3269
+ if (!cancelled) setReferralCode(data.referral_code);
3270
+ }).catch(() => {
3271
+ if (!cancelled) setReferralCode(null);
3272
+ }).finally(() => {
3273
+ if (!cancelled) setLoading(false);
3274
+ });
3275
+ return () => {
3276
+ cancelled = true;
3277
+ };
3278
+ }, [user?.id, options?.url, options?.referralCode, options?.autoFetchReferral, client]);
3279
+ const shareUrl = react.useMemo(() => {
3280
+ const raw = options?.url || (typeof window !== "undefined" ? window.location.href : "");
3281
+ if (!raw) return raw;
3282
+ try {
3283
+ const base = typeof window !== "undefined" ? window.location.origin : void 0;
3284
+ const u = new URL(raw, base);
3285
+ if (referralCode) {
3286
+ u.searchParams.set("rc", referralCode);
3287
+ }
3288
+ return u.toString();
3289
+ } catch {
3290
+ return raw;
3291
+ }
3292
+ }, [options?.url, referralCode]);
3293
+ const copyLink = react.useCallback(async () => {
3294
+ try {
3295
+ await navigator.clipboard.writeText(shareUrl);
3296
+ } catch {
3297
+ try {
3298
+ const textarea = document.createElement("textarea");
3299
+ textarea.value = shareUrl;
3300
+ textarea.style.position = "fixed";
3301
+ textarea.style.opacity = "0";
3302
+ document.body.appendChild(textarea);
3303
+ textarea.select();
3304
+ document.execCommand("copy");
3305
+ document.body.removeChild(textarea);
3306
+ } catch {
3307
+ return false;
3308
+ }
3309
+ }
3310
+ setCopied(true);
3311
+ setTimeout(() => setCopied(false), 2e3);
3312
+ return true;
3313
+ }, [shareUrl]);
3314
+ return { shareUrl, referralCode, copyLink, copied, loading };
3315
+ }
3225
3316
 
3226
3317
  // src/validation.ts
3227
3318
  var phoneCountries = [
@@ -3532,6 +3623,7 @@ exports.usePushNotifications = usePushNotifications;
3532
3623
  exports.useRealtime = useRealtime;
3533
3624
  exports.useScaleMule = useScaleMule;
3534
3625
  exports.useScaleMuleClient = useScaleMuleClient;
3626
+ exports.useShare = useShare;
3535
3627
  exports.useUser = useUser;
3536
3628
  exports.validateForm = validateForm;
3537
3629
  exports.validators = validators;
package/dist/index.mjs CHANGED
@@ -3136,9 +3136,18 @@ function usePushNotifications(options = {}) {
3136
3136
  setIsSupported(supported);
3137
3137
  setPermission(manager.getPermissionState());
3138
3138
  if (supported) {
3139
+ const storedTokenId = manager.getTokenId();
3139
3140
  manager.isSubscribed().then((sub) => {
3140
- setIsSubscribed(sub);
3141
- setTokenId(manager.getTokenId());
3141
+ if (!sub && storedTokenId) {
3142
+ manager.unsubscribe().catch(() => {
3143
+ });
3144
+ setIsSubscribed(false);
3145
+ setTokenId(null);
3146
+ setPermission(manager.getPermissionState());
3147
+ } else {
3148
+ setIsSubscribed(sub);
3149
+ setTokenId(manager.getTokenId());
3150
+ }
3142
3151
  });
3143
3152
  }
3144
3153
  }, [manager]);
@@ -3148,10 +3157,19 @@ function usePushNotifications(options = {}) {
3148
3157
  if (event.data?.type === "push-received") {
3149
3158
  onNotificationRef.current?.(event.data.payload);
3150
3159
  }
3160
+ if (event.data?.type === "push-subscription-expired") {
3161
+ if (manager) {
3162
+ manager.unsubscribe().catch(() => {
3163
+ });
3164
+ }
3165
+ setIsSubscribed(false);
3166
+ setTokenId(null);
3167
+ setPermission(typeof Notification !== "undefined" ? Notification.permission : "unsupported");
3168
+ }
3151
3169
  }
3152
3170
  navigator.serviceWorker.addEventListener("message", handleMessage);
3153
3171
  return () => navigator.serviceWorker.removeEventListener("message", handleMessage);
3154
- }, []);
3172
+ }, [manager]);
3155
3173
  useEffect(() => {
3156
3174
  if (!manager) return;
3157
3175
  const currentUserId = user?.id || null;
@@ -3220,6 +3238,79 @@ function usePushNotifications(options = {}) {
3220
3238
  tokenId
3221
3239
  };
3222
3240
  }
3241
+ function useShare(options) {
3242
+ const { client, user } = useScaleMule();
3243
+ const [referralCode, setReferralCode] = useState(
3244
+ options?.referralCode ?? null
3245
+ );
3246
+ const [loading, setLoading] = useState(false);
3247
+ const [copied, setCopied] = useState(false);
3248
+ useEffect(() => {
3249
+ if (!user) {
3250
+ setReferralCode(options?.referralCode ?? null);
3251
+ setLoading(false);
3252
+ return;
3253
+ }
3254
+ if (!options?.autoFetchReferral) {
3255
+ setLoading(false);
3256
+ return;
3257
+ }
3258
+ if (options?.referralCode) {
3259
+ setReferralCode(options.referralCode);
3260
+ setLoading(false);
3261
+ return;
3262
+ }
3263
+ setReferralCode(null);
3264
+ let cancelled = false;
3265
+ setLoading(true);
3266
+ client.get("/v1/referrals/me").then((data) => {
3267
+ if (!cancelled) setReferralCode(data.referral_code);
3268
+ }).catch(() => {
3269
+ if (!cancelled) setReferralCode(null);
3270
+ }).finally(() => {
3271
+ if (!cancelled) setLoading(false);
3272
+ });
3273
+ return () => {
3274
+ cancelled = true;
3275
+ };
3276
+ }, [user?.id, options?.url, options?.referralCode, options?.autoFetchReferral, client]);
3277
+ const shareUrl = useMemo(() => {
3278
+ const raw = options?.url || (typeof window !== "undefined" ? window.location.href : "");
3279
+ if (!raw) return raw;
3280
+ try {
3281
+ const base = typeof window !== "undefined" ? window.location.origin : void 0;
3282
+ const u = new URL(raw, base);
3283
+ if (referralCode) {
3284
+ u.searchParams.set("rc", referralCode);
3285
+ }
3286
+ return u.toString();
3287
+ } catch {
3288
+ return raw;
3289
+ }
3290
+ }, [options?.url, referralCode]);
3291
+ const copyLink = useCallback(async () => {
3292
+ try {
3293
+ await navigator.clipboard.writeText(shareUrl);
3294
+ } catch {
3295
+ try {
3296
+ const textarea = document.createElement("textarea");
3297
+ textarea.value = shareUrl;
3298
+ textarea.style.position = "fixed";
3299
+ textarea.style.opacity = "0";
3300
+ document.body.appendChild(textarea);
3301
+ textarea.select();
3302
+ document.execCommand("copy");
3303
+ document.body.removeChild(textarea);
3304
+ } catch {
3305
+ return false;
3306
+ }
3307
+ }
3308
+ setCopied(true);
3309
+ setTimeout(() => setCopied(false), 2e3);
3310
+ return true;
3311
+ }, [shareUrl]);
3312
+ return { shareUrl, referralCode, copyLink, copied, loading };
3313
+ }
3223
3314
 
3224
3315
  // src/validation.ts
3225
3316
  var phoneCountries = [
@@ -3512,4 +3603,4 @@ function createSafeLogger(prefix) {
3512
3603
  };
3513
3604
  }
3514
3605
 
3515
- export { ScaleMuleApiError, ScaleMuleClient, ScaleMuleProvider, composePhone, createClient, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useUser, validateForm, validators };
3606
+ export { ScaleMuleApiError, ScaleMuleClient, ScaleMuleProvider, composePhone, createClient, createSafeLogger, normalizePhone, phoneCountries, sanitizeForLog, useAnalytics, useAuth, useBilling, useContent, useFeatureFlags, usePushNotifications, useRealtime, useScaleMule, useScaleMuleClient, useShare, useUser, validateForm, validators };
@@ -1836,8 +1836,12 @@ function apiHandler(handler, options) {
1836
1836
  const custom = options.onError(error);
1837
1837
  if (custom) return custom;
1838
1838
  }
1839
+ const safeMessage = error.status >= 500 ? "An unexpected error occurred" : error.message;
1840
+ if (error.status >= 500) {
1841
+ console.error("Internal API error:", error.message);
1842
+ }
1839
1843
  return Response.json(
1840
- { success: false, error: { code: error.code, message: error.message } },
1844
+ { success: false, error: { code: error.code, message: safeMessage } },
1841
1845
  { status: error.status }
1842
1846
  );
1843
1847
  }
@@ -1834,8 +1834,12 @@ function apiHandler(handler, options) {
1834
1834
  const custom = options.onError(error);
1835
1835
  if (custom) return custom;
1836
1836
  }
1837
+ const safeMessage = error.status >= 500 ? "An unexpected error occurred" : error.message;
1838
+ if (error.status >= 500) {
1839
+ console.error("Internal API error:", error.message);
1840
+ }
1837
1841
  return Response.json(
1838
- { success: false, error: { code: error.code, message: error.message } },
1842
+ { success: false, error: { code: error.code, message: safeMessage } },
1839
1843
  { status: error.status }
1840
1844
  );
1841
1845
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/nextjs",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "description": "ScaleMule SDK for Next.js applications - authentication, storage, and user management",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -52,7 +52,7 @@
52
52
  "clean": "rm -rf dist"
53
53
  },
54
54
  "dependencies": {
55
- "@scalemule/sdk": "^0.0.23"
55
+ "@scalemule/sdk": ">=0.0.23"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "next": ">=14.0.0",