Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 78 additions & 40 deletions components/notifications/NotificationsContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
"use client";

import React, { createContext, useContext, useEffect, useMemo } from "react";
import React, {
createContext,
useContext,
useEffect,
useMemo,
useRef,
} from "react";
import {
PushNotifications,
PushNotificationSchema,
Expand Down Expand Up @@ -40,10 +46,18 @@ export const NotificationsProvider: React.FC<{ children: React.ReactNode }> = ({
const { isCapacitor, isIos } = useCapacitor();
const { connectedProfile } = useAuth();
const router = useRouter();
const initializationRef = useRef<string | null>(null);

useEffect(() => {
initializeNotifications(connectedProfile ?? undefined);
}, [connectedProfile]);
const profileId = connectedProfile?.id ?? null;
if (
isCapacitor &&
initializationRef.current !== profileId
) {
initializationRef.current = profileId;
initializeNotifications(connectedProfile ?? undefined);
}
}, [connectedProfile, isCapacitor]);

const initializeNotifications = async (profile?: ApiIdentity) => {
try {
Expand All @@ -57,50 +71,74 @@ export const NotificationsProvider: React.FC<{ children: React.ReactNode }> = ({
};

const initializePushNotifications = async (profile?: ApiIdentity) => {
await PushNotifications.removeAllListeners();
try {
await PushNotifications.removeAllListeners();

const stableDeviceId = await getStableDeviceId();
const stableDeviceId = await getStableDeviceId();

const deviceInfo = await Device.getInfo();
const deviceInfo = await Device.getInfo();

await PushNotifications.addListener("registration", async (token) => {
registerPushNotification(
stableDeviceId,
deviceInfo,
token.value,
profile
);
});

await PushNotifications.addListener("registrationError", (error) => {
console.error("Push registration error: ", error);
});

await PushNotifications.addListener(
"pushNotificationReceived",
(notification) => {
console.log("Push notification received: ", notification);
}
);

await PushNotifications.addListener(
"pushNotificationActionPerformed",
async (action) => {
await handlePushNotificationAction(
router,
action.notification,
await PushNotifications.addListener("registration", async (token) => {
registerPushNotification(
stableDeviceId,
deviceInfo,
token.value,
profile
);
}
);
});

const permStatus = await PushNotifications.requestPermissions();
console.log("Push permission status", permStatus);
await PushNotifications.addListener("registrationError", (error) => {
console.error("Push registration error: ", error);
});

if (permStatus.receive === "granted") {
await PushNotifications.register();
} else {
console.warn("Push notifications permission not granted");
await PushNotifications.addListener(
"pushNotificationReceived",
(notification) => {
console.log("Push notification received: ", notification);
}
);

await PushNotifications.addListener(
"pushNotificationActionPerformed",
async (action) => {
await handlePushNotificationAction(
router,
action.notification,
profile
);
}
);

const permStatus = await PushNotifications.requestPermissions();
console.log("Push permission status", permStatus);

if (permStatus.receive === "granted") {
if (isIos) {
await new Promise((resolve) => setTimeout(resolve, 500));
}
try {
await PushNotifications.register();
} catch (registerError: any) {
const errorMessage = registerError?.message || String(registerError);
if (
errorMessage.includes(
"capacitorDidRegisterForRemoteNotifications"
) ||
errorMessage.includes("didRegisterForRemoteNotifications")
) {
console.warn(
"iOS push notification registration callback issue. This may occur if the native delegate is not properly configured.",
registerError
);
} else {
throw registerError;
}
}
} else {
console.warn("Push notifications permission not granted");
}
} catch (error) {
console.error("Error in initializePushNotifications", error);
}
};

Expand Down
10 changes: 7 additions & 3 deletions components/rememes/RememeAddPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,13 @@ export default function RememeAddPage() {
async function fetchTdh() {
commonApiFetch<ConsolidatedTDH>({
endpoint: `tdh/consolidation/${connectedProfile?.consolidation_key}`,
}).then((response) => {
setUserTDH(response);
});
})
.then((response) => {
setUserTDH(response);
})
.catch(() => {
setUserTDH(undefined);
});
}
if (connectedProfile?.consolidation_key) {
fetchTdh();
Expand Down
22 changes: 14 additions & 8 deletions components/the-memes/MemePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,16 +226,22 @@ export default function MemePage({ nftId }: { readonly nftId: string }) {
}, [nftId, connectedWallets]);

useEffect(() => {
if (connectedWallets.length > 0 && nftId) {
if (connectedWallets.length > 0 && nftId && connectedProfile?.consolidation_key) {
commonApiFetch<ConsolidatedTDH>({
endpoint: `tdh/consolidation/${connectedProfile?.consolidation_key}`,
}).then((response) => {
setMyOwner(response);
setMyTDH(response.memes.find((m) => m.id === parseInt(nftId)));
setMyRank(response.memes_ranks.find((m) => m.id === parseInt(nftId)));
});
endpoint: `tdh/consolidation/${connectedProfile.consolidation_key}`,
})
.then((response) => {
setMyOwner(response);
setMyTDH(response.memes.find((m) => m.id === parseInt(nftId)));
setMyRank(response.memes_ranks.find((m) => m.id === parseInt(nftId)));
})
.catch(() => {
setMyOwner(undefined);
setMyTDH(undefined);
setMyRank(undefined);
});
}
}, [nftId, connectedWallets]);
}, [nftId, connectedWallets, connectedProfile?.consolidation_key]);

function printContent() {
return (
Expand Down
8 changes: 7 additions & 1 deletion components/user/subscriptions/UserPageSubscriptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export default function UserPageSubscriptions(
);
}, [
fetchingDetails,
fetchAirdropAddress,
fetchingAirdropAddress,
fetchingTopUpHistory,
fetchingMemeSubscriptions,
fetchingSubscriptionLogs,
Expand All @@ -135,6 +135,9 @@ export default function UserPageSubscriptions(
.then((data) => {
setDetails(data);
})
.catch(() => {
setDetails(undefined);
})
.finally(() => {
setFetchingDetails(false);
});
Expand All @@ -151,6 +154,9 @@ export default function UserPageSubscriptions(
.then((data) => {
setAirdropResult(data);
})
.catch(() => {
setAirdropResult(undefined);
})
.finally(() => {
setFetchingAirdropAddress(false);
});
Expand Down
30 changes: 30 additions & 0 deletions config/sentryProbes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const PROBE_PATTERNS = [
".jsp",
".php",
".asp",
".aspx",
"/exportimport/",
"/wp-admin",
"/wp-login",
"/cgi-bin/",
"/manager/html", // Tomcat
"/admin/login.jsp", // common Java probe
];

const probeTags = {
security_probe: "true",
probe_type: "generic-exploit-scan",
};

export function tagSecurityProbes(event: any) {
const url = (event?.request?.url || "").toLowerCase();

if (PROBE_PATTERNS.some((p) => url.includes(p))) {
event.level = "info";
event.tags = event.tags
? { ...event.tags, ...probeTags }
: { ...probeTags };
}

return event;
}
9 changes: 7 additions & 2 deletions hooks/useWaveIsTyping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,13 @@ export function useWaveIsTyping(
});
};

socket.addEventListener("message", onMessage);
return () => socket.removeEventListener("message", onMessage);
const currentSocket = socket;
currentSocket.addEventListener("message", onMessage);
return () => {
if (currentSocket) {
currentSocket.removeEventListener("message", onMessage);
}
};
}, [socket, waveId, myHandle]);

/* ----- 3. Periodic cleanup + state update ------------------------ */
Expand Down
45 changes: 41 additions & 4 deletions instrumentation-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
// The added config here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/

import { publicEnv } from "@/config/env";
import {
INDEXEDDB_ERROR_MESSAGE,
isIndexedDBError,
} from "@/utils/error-sanitizer";
import * as Sentry from "@sentry/nextjs";
import {publicEnv} from "@/config/env";

const sentryEnabled = !!publicEnv.SENTRY_DSN;
const isProduction = publicEnv.NODE_ENV === "production";
Expand All @@ -14,9 +18,7 @@ Sentry.init({
enabled: sentryEnabled,

// Add optional integrations for additional features
integrations: [
Sentry.replayIntegration(),
],
integrations: [Sentry.replayIntegration()],

// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 0.1,
Expand All @@ -34,6 +36,41 @@ Sentry.init({
// Enable sending user PII (Personally Identifiable Information)
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
sendDefaultPii: true,

beforeSend(event, hint) {
const error = hint.originalException || hint.syntheticException;

if (error && isIndexedDBError(error)) {
event.level = "warning";
event.tags = {
...event.tags,
errorType: "indexeddb",
handled: true,
};
event.fingerprint = ["indexeddb-connection-lost"];
}

return event;
},
});

if (globalThis.window !== undefined) {
globalThis.window.addEventListener("error", (event) => {
if (isIndexedDBError(event.error)) {
event.preventDefault();
console.warn(`[IndexedDB] ${INDEXEDDB_ERROR_MESSAGE}`, event.error);
}
});

globalThis.window.addEventListener("unhandledrejection", (event) => {
if (isIndexedDBError(event.reason)) {
event.preventDefault();
console.warn(
`[IndexedDB] Unhandled promise rejection: ${INDEXEDDB_ERROR_MESSAGE}`,
event.reason
);
}
});
}

export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
Loading