Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 16 additions & 0 deletions apps/web/src/api/notifications-ws-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ export class NotificationsWebSocket {
return i18next.t("notification.bookmark", { source });
case "follow":
return i18next.t("notification.followed", { source });
case "unfollow":
return i18next.t("notification.unfollowed", { source });
case "ignore":
return i18next.t("notification.ignored", { source });
case "blacklist":
return i18next.t("notification.blacklisted", { source });
case "reply":
return i18next.t("notification.replied", { source });
case "reblog":
Expand Down Expand Up @@ -180,6 +186,10 @@ export class NotificationsWebSocket {
// keeps this table identical to the push one.
return toEntry(data.source, data.extra?.permlink);
case "follow":
case "unfollow":
case "ignore":
case "blacklist":
// The whole follow family points at the actor's profile, never at an entry.
return toProfile(data.source);
case "transfer":
case "delegations":
Expand Down Expand Up @@ -357,6 +367,12 @@ export class NotificationsWebSocket {
case "mention":
return NotifyTypes.MENTION;
case "follow":
case "unfollow":
case "ignore":
case "blacklist":
// All four share ACTIVITY_MAIN_TYPE_FOLLOW server side, so they ride one
// toggle. Leaving them unmapped returned null, which this gate reads as
// always-allowed, so the user's Follows switch did not apply to them.
return NotifyTypes.FOLLOW;
case "reply":
return NotifyTypes.COMMENT;
Expand Down
8 changes: 6 additions & 2 deletions apps/web/src/entities/ws-notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ export interface WsBookmarkNotification extends BaseWsNotification {
}

export interface WsFollowNotification extends BaseWsNotification {
type: "follow";
// The websocket carries the whole follow family, not just "follow": enotify's
// str_activity_type() emits unfollow / ignore / blacklist too, and they all share
// ACTIVITY_MAIN_TYPE_FOLLOW. Narrowing this to "follow" is what left the other three
// without a body, a link or a toggle mapping.
type: "follow" | "unfollow" | "ignore" | "blacklist";
extra: {
what: string[];
};
Expand Down Expand Up @@ -225,7 +229,7 @@ export interface ApiMentionNotification extends BaseAPiNotification {
}

export interface ApiFollowNotification extends BaseAPiNotification {
type: "follow" | "unfollow" | "ignore";
type: "follow" | "unfollow" | "ignore" | "blacklist";
follower: string;
following: string;
blog: boolean;
Expand Down
4 changes: 4 additions & 0 deletions apps/web/src/features/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -2131,6 +2131,9 @@
"favorite": "{{source}} made new post",
"bookmark": "{{source}} replied to bookmarked content",
"followed": "{{source}} followed you",
"unfollowed": "{{source}} unfollowed you",
"ignored": "{{source}} ignored you",
"blacklisted": "{{source}} blacklisted you",
"replied": "{{source}} replied you",
"reblogged": "{{source}} reblogged your post",
"transfer": "{{source}} sent you {{amount}}",
Expand Down Expand Up @@ -2172,6 +2175,7 @@
"followed-str": "followed",
"unfollowed-str": "unfollowed",
"ignored-str": "ignored",
"blacklisted-str": "blacklisted",
"reblog-str": "reblogged post",
"transfer-str": "sent",
"delegations-str": "delegated",
Expand Down
10 changes: 9 additions & 1 deletion apps/web/src/features/shared/notifications/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@

.follow-label,
.unfollow-label,
.ignore-label {
.ignore-label,
.blacklist-label {
border-radius: 25px;
@apply text-white;
display: inline-block;
Expand All @@ -241,6 +242,13 @@
.ignore-label {
@apply bg-red-030;
}

// Darkest of the family, matching that blacklist is its harshest member.
// Deliberately not a brighter red: red-040 (#ec3323) is only 4.15:1 against
// white, under AA for 12px text, while red-030 is already taken by ignore.
.blacklist-label {
@apply bg-blue-dark-grey;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ export const NotificationListItem = memo(function NotificationListItem({
)}
{(notification.type === "follow" ||
notification.type === "unfollow" ||
notification.type === "ignore") && (
notification.type === "ignore" ||
notification.type === "blacklist") && (
<NotificationFollowType sourceLink={sourceLink} notification={notification} />
)}
{notification.type === "reblog" && (
Expand Down Expand Up @@ -252,7 +253,7 @@ export const NotificationListItem = memo(function NotificationListItem({
)}
{![
"vote", "unvote", "reply", "mention", "favorites", "bookmarks",
"follow", "unfollow", "ignore", "reblog", "transfer", "delegations",
"follow", "unfollow", "ignore", "blacklist", "reblog", "transfer", "delegations",
"checkins", "checkin", "payouts", "monthly-posts", "monthly_posts",
"spin", "inactive", "referral", "account_update", "weekly_earnings",
"scheduled_published"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export function NotificationFollowType({ notification, sourceLink }: Props) {
{notification.type === "ignore" && (
<span className="ignore-label">{i18next.t("notifications.ignored-str")}</span>
)}
{notification.type === "blacklist" && (
<span className="blacklist-label">{i18next.t("notifications.blacklisted-str")}</span>
)}
</div>
</div>
);
Expand Down
8 changes: 8 additions & 0 deletions apps/web/src/specs/api/notifications-ws-link.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ const entry = (type: string, permlink = "a-permlink") => ({
extra: { permlink }
});

describe("NotificationsWebSocket.getLink follow family", () => {
// The whole family points at the actor's profile. None of them carry a permlink, so
// resolving one as an entry would build a URL that cannot exist.
it.each(["follow", "unfollow", "ignore", "blacklist"])("links %s to the actor", (type) => {
expect(getLink({ type, source: "actor", target: "recipient" })).toBe("/@actor");
});
});

describe("NotificationsWebSocket.getLink entry authorship", () => {
it.each([
// The recipient's own post was acted on.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ describe("NotificationsWebSocket.getNotificationType", () => {
expect(missing).toEqual([]);
});

it("maps the whole follow family to the Follows toggle", () => {
// enotify's str_activity_type() emits all four over the websocket and they share
// ACTIVITY_MAIN_TYPE_FOLLOW, so one toggle governs them. Leaving unfollow/ignore/
// blacklist unmapped returned null, which the delivery gate reads as always-allowed,
// so turning Follows off did not silence them.
for (const wireType of ["follow", "unfollow", "ignore", "blacklist"]) {
expect(ws.getNotificationType(wireType)).toBe(NotifyTypes.FOLLOW);
}
});

it("returns null for types with no per-type toggle", () => {
// These are intentionally always-allowed, gated only by the global switch.
expect(ws.getNotificationType("checkins")).toBeNull();
Expand Down Expand Up @@ -104,3 +114,24 @@ describe("NotificationsWebSocket body for account_update", () => {
spy.mockRestore();
});
});

/**
* getBody() returning "" makes the delivery path drop the notification entirely, so a
* type the server can send but this switch has no branch for is silently undeliverable.
* unfollow, ignore and blacklist were in exactly that state.
*/
describe("NotificationsWebSocket body for the follow family", () => {
const getBody = (type: string) =>
(NotificationsWebSocket as any).getBody({ type, source: "alice", extra: { what: [] } });

it.each([
["follow", "notification.followed"],
["unfollow", "notification.unfollowed"],
["ignore", "notification.ignored"],
["blacklist", "notification.blacklisted"]
])("gives %s a non-empty body", (type, expected) => {
const body = getBody(type);
expect(body).toBe(expected);
expect(body).not.toBe("");
});
});
6 changes: 6 additions & 0 deletions packages/sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 2.3.95

### Patch Changes

- fix(notifications): handle the whole follow family, not just follow (#1708)

## 2.3.94

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/dist/browser/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6775,7 +6775,7 @@ interface WsBookmarkNotification extends BaseWsNotification {
};
}
interface WsFollowNotification extends BaseWsNotification {
type: "follow";
type: "follow" | "unfollow" | "ignore" | "blacklist";
extra: {
what: string[];
};
Expand Down Expand Up @@ -6864,7 +6864,7 @@ interface ApiMentionNotification extends BaseAPiNotification {
deck?: boolean;
}
interface ApiFollowNotification extends BaseAPiNotification {
type: "follow" | "unfollow" | "ignore";
type: "follow" | "unfollow" | "ignore" | "blacklist";
follower: string;
following: string;
blog: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ecency/sdk",
"private": false,
"version": "2.3.94",
"version": "2.3.95",
"description": "Ecency SDK",
"repository": {
"type": "git",
Expand Down
8 changes: 6 additions & 2 deletions packages/sdk/src/modules/notifications/types/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ export interface WsBookmarkNotification extends BaseWsNotification {
}

export interface WsFollowNotification extends BaseWsNotification {
type: "follow";
// The websocket carries the whole follow family, not just "follow": enotify's
// str_activity_type() emits unfollow / ignore / blacklist too, and they all share
// ACTIVITY_MAIN_TYPE_FOLLOW. Narrowing this to "follow" is what left the other three
// without a body, a link or a toggle mapping.
type: "follow" | "unfollow" | "ignore" | "blacklist";
extra: {
what: string[];
};
Expand Down Expand Up @@ -168,7 +172,7 @@ export interface ApiMentionNotification extends BaseAPiNotification {
}

export interface ApiFollowNotification extends BaseAPiNotification {
type: "follow" | "unfollow" | "ignore";
type: "follow" | "unfollow" | "ignore" | "blacklist";
follower: string;
following: string;
blog: boolean;
Expand Down
7 changes: 7 additions & 0 deletions packages/wallets/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @ecency/wallets

## 5.0.95

### Patch Changes

- Updated dependencies []:
- @ecency/sdk@2.3.95

## 5.0.94

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/wallets/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ecency/wallets",
"private": false,
"version": "5.0.94",
"version": "5.0.95",
"description": "Ecency wallets",
"repository": {
"type": "git",
Expand Down