@rocapine/community-core 0.11.1 → 0.12.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 +8 -4
- package/lib/config.d.ts +18 -3
- package/lib/config.js +5 -3
- package/lib/identity.d.ts +6 -0
- package/lib/identity.js +33 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.js +4 -2
- package/lib/service.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,9 +24,13 @@ const config: CommunityConfig = {
|
|
|
24
24
|
|
|
25
25
|
// Product vocabulary
|
|
26
26
|
appName: "My App", // pushes, inbox, broadcast fallback title
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
27
|
+
// Optional. Every profile starts as "<label> <digits>" / @<handle>-<digits>,
|
|
28
|
+
// assigned on the first launch in the user's language and never re-translated.
|
|
29
|
+
// `label` may be a function of the host locale; defaults to "User" / "user".
|
|
30
|
+
defaultName: {
|
|
31
|
+
label: (locale) => (locale.startsWith("fr") ? "Membre" : "Member"),
|
|
32
|
+
handle: "member",
|
|
33
|
+
},
|
|
30
34
|
|
|
31
35
|
// Topics — nothing hardcoded. `id` resolves to `topics.<id>` in the UI
|
|
32
36
|
// package's i18n catalog (see packages/ui/README.md); the app supplies
|
|
@@ -99,7 +103,7 @@ top-up), without forking the query or mapping layer:
|
|
|
99
103
|
import type { CommunityConfig } from "@rocapine/community-core";
|
|
100
104
|
|
|
101
105
|
const config: CommunityConfig = {
|
|
102
|
-
// ...supabase, appName,
|
|
106
|
+
// ...supabase, appName, defaultName, topics, modules...
|
|
103
107
|
feed: {
|
|
104
108
|
// Appended to the posts `select` in every posts-table query
|
|
105
109
|
// (fetchFeedPage, fetchUserPosts, searchPosts). Only `[a-z0-9_]+`
|
package/lib/config.d.ts
CHANGED
|
@@ -60,11 +60,22 @@ export type CommunityFeedConfig = {
|
|
|
60
60
|
* any `extraPostColumns`) it was built from. */
|
|
61
61
|
transformPost?: (post: FeedPost, row: Record<string, unknown>) => FeedPost;
|
|
62
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Every profile starts as "<label> <digits>" / "@<handle>-<digits>", assigned
|
|
65
|
+
* once on the user's first launch and never re-translated afterwards (a name is
|
|
66
|
+
* an identity, not content). `label` may depend on the host locale ("Frère" /
|
|
67
|
+
* "Brother"); keep it a noun, at most 17 characters. `handle` is the ASCII
|
|
68
|
+
* handle prefix ([a-z0-9], at most 12 characters).
|
|
69
|
+
*/
|
|
70
|
+
export type CommunityDefaultName = {
|
|
71
|
+
label: string | ((locale: string) => string);
|
|
72
|
+
handle: string;
|
|
73
|
+
};
|
|
63
74
|
export type CommunityConfig = {
|
|
64
75
|
supabase: SupabaseClient | null;
|
|
65
76
|
appName: string;
|
|
66
|
-
/**
|
|
67
|
-
|
|
77
|
+
/** Defaults to `DEFAULT_NAME` ("User" / "user"). */
|
|
78
|
+
defaultName?: CommunityDefaultName;
|
|
68
79
|
topics: CommunityTopicDef[];
|
|
69
80
|
modules: CommunityModules;
|
|
70
81
|
host?: CommunityHostAdapters;
|
|
@@ -78,10 +89,14 @@ export type ResolvedCommunityConfig = CommunityConfig & {
|
|
|
78
89
|
host: RequiredCommunityHostAdapters;
|
|
79
90
|
feed: CommunityFeedConfig;
|
|
80
91
|
requireClient(): SupabaseClient;
|
|
81
|
-
/** `
|
|
92
|
+
/** `defaultName` resolved against the host locale. */
|
|
93
|
+
defaultName: CommunityDefaultName;
|
|
94
|
+
/** `defaultName.label` resolved against `host.getLocale()`: the name shown for a
|
|
95
|
+
* profile that has none yet (one that has not relaunched since defaults existed). */
|
|
82
96
|
authorFallback(): string;
|
|
83
97
|
composeTopics(): CommunityTopicDef[];
|
|
84
98
|
isOfficialTopic(id: string): boolean;
|
|
85
99
|
};
|
|
100
|
+
export declare const DEFAULT_NAME: CommunityDefaultName;
|
|
86
101
|
export declare function resolveConfig(config: CommunityConfig): ResolvedCommunityConfig;
|
|
87
102
|
export {};
|
package/lib/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CommunityDisabledError = void 0;
|
|
3
|
+
exports.DEFAULT_NAME = exports.CommunityDisabledError = void 0;
|
|
4
4
|
exports.resolveConfig = resolveConfig;
|
|
5
5
|
class CommunityDisabledError extends Error {
|
|
6
6
|
constructor() {
|
|
@@ -9,6 +9,7 @@ class CommunityDisabledError extends Error {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
exports.CommunityDisabledError = CommunityDisabledError;
|
|
12
|
+
exports.DEFAULT_NAME = { label: "User", handle: "user" };
|
|
12
13
|
let warned = false;
|
|
13
14
|
const defaultHost = {
|
|
14
15
|
getDisplayName: () => null,
|
|
@@ -30,6 +31,7 @@ function resolveConfig(config) {
|
|
|
30
31
|
...config,
|
|
31
32
|
host,
|
|
32
33
|
feed: config.feed ?? {},
|
|
34
|
+
defaultName: config.defaultName ?? exports.DEFAULT_NAME,
|
|
33
35
|
requireClient() {
|
|
34
36
|
if (!resolved.supabase) {
|
|
35
37
|
if (!warned) {
|
|
@@ -41,8 +43,8 @@ function resolveConfig(config) {
|
|
|
41
43
|
return resolved.supabase;
|
|
42
44
|
},
|
|
43
45
|
authorFallback() {
|
|
44
|
-
const
|
|
45
|
-
return typeof
|
|
46
|
+
const label = resolved.defaultName.label;
|
|
47
|
+
return typeof label === "function" ? label(host.getLocale()) : label;
|
|
46
48
|
},
|
|
47
49
|
composeTopics() {
|
|
48
50
|
return resolved.topics.filter((topic) => !topic.officialOnly);
|
package/lib/identity.d.ts
CHANGED
|
@@ -13,6 +13,12 @@ export declare function ensureIdentity(cfg: ResolvedCommunityConfig): Promise<st
|
|
|
13
13
|
* fresh anonymous identity.
|
|
14
14
|
*/
|
|
15
15
|
export declare function resetIdentity(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Account deletion: put the default name and handle back and clear bio,
|
|
18
|
+
* avatar and the user's own edits, so kept posts no longer carry the
|
|
19
|
+
* pseudonym. Resolves false when the backend could not be reached.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resetProfileIdentity(cfg: ResolvedCommunityConfig): Promise<boolean>;
|
|
16
22
|
/**
|
|
17
23
|
* Mirror the host's display name, locale and analytics ids onto the
|
|
18
24
|
* profiles row. Safe to call on every app open / community open: each field
|
package/lib/identity.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ensureIdentity = ensureIdentity;
|
|
4
4
|
exports.resetIdentity = resetIdentity;
|
|
5
|
+
exports.resetProfileIdentity = resetProfileIdentity;
|
|
5
6
|
exports.syncProfileFromHost = syncProfileFromHost;
|
|
6
7
|
// One anonymous Supabase identity per install. signInAnonymously() runs once;
|
|
7
8
|
// afterwards getSession() restores the same user (and uuid) from AsyncStorage.
|
|
@@ -41,8 +42,10 @@ function resetIdentity() {
|
|
|
41
42
|
async function bootstrap(cfg) {
|
|
42
43
|
const client = cfg.requireClient();
|
|
43
44
|
const { data } = await client.auth.getSession();
|
|
44
|
-
if (data.session)
|
|
45
|
+
if (data.session) {
|
|
46
|
+
await assignDefaultProfile(cfg);
|
|
45
47
|
return data.session.user.id;
|
|
48
|
+
}
|
|
46
49
|
const { data: signIn, error } = await client.auth.signInAnonymously();
|
|
47
50
|
if (error || !signIn.user)
|
|
48
51
|
throw error ?? new Error("anonymous sign-in returned no user");
|
|
@@ -54,8 +57,37 @@ async function bootstrap(cfg) {
|
|
|
54
57
|
.from("profiles")
|
|
55
58
|
.update({ amplitude_id: amplitudeId ?? null, revenuecat_id: revenuecatId ?? null })
|
|
56
59
|
.eq("id", uid);
|
|
60
|
+
await assignDefaultProfile(cfg);
|
|
57
61
|
return uid;
|
|
58
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Give the profile its default name and handle ("Frère 4821" / "@brother-4821")
|
|
65
|
+
* when it has none yet — once per launch, idempotent server-side (core/009).
|
|
66
|
+
* Best effort: an older backend without the RPC keeps the label-only fallback.
|
|
67
|
+
*/
|
|
68
|
+
async function assignDefaultProfile(cfg) {
|
|
69
|
+
const { label, handle } = cfg.defaultName;
|
|
70
|
+
try {
|
|
71
|
+
await cfg.requireClient().rpc("assign_default_profile", {
|
|
72
|
+
p_label: typeof label === "function" ? label(cfg.host.getLocale()) : label,
|
|
73
|
+
p_handle: handle,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Offline or older backend: retried on the next launch.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Account deletion: put the default name and handle back and clear bio,
|
|
82
|
+
* avatar and the user's own edits, so kept posts no longer carry the
|
|
83
|
+
* pseudonym. Resolves false when the backend could not be reached.
|
|
84
|
+
*/
|
|
85
|
+
async function resetProfileIdentity(cfg) {
|
|
86
|
+
if (!cfg.supabase)
|
|
87
|
+
return false;
|
|
88
|
+
const { error } = await cfg.supabase.rpc("reset_profile_identity");
|
|
89
|
+
return !error;
|
|
90
|
+
}
|
|
59
91
|
/**
|
|
60
92
|
* Mirror the host's display name, locale and analytics ids onto the
|
|
61
93
|
* profiles row. Safe to call on every app open / community open: each field
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { CommunityTopicDef, CommunityModules, CommunityHostAdapters, CommunityFeedConfig, CommunityConfig, ResolvedCommunityConfig, } from "./config";
|
|
2
|
-
export { CommunityDisabledError, resolveConfig } from "./config";
|
|
1
|
+
export type { CommunityTopicDef, CommunityModules, CommunityHostAdapters, CommunityFeedConfig, CommunityConfig, CommunityDefaultName, ResolvedCommunityConfig, } from "./config";
|
|
2
|
+
export { CommunityDisabledError, DEFAULT_NAME, resolveConfig } from "./config";
|
|
3
3
|
export { readerLocale, languageOf } from "./locale";
|
|
4
4
|
export { CommunityProvider, useCommunityConfig } from "./provider";
|
|
5
5
|
export type { PostRow, PollOptionRow, CommentRow, ProfileRow, PollOption, FeedPoll, PollData, ReactionData, FeedPost, ThreadComment, PostImage, LocalImage, CommunityProfile, TranslationInfo, TranslationRowEmbed, } from "./models";
|
|
@@ -8,7 +8,7 @@ export type { TimeAgoParts } from "./time";
|
|
|
8
8
|
export { timeAgo, timeAgoParts } from "./time";
|
|
9
9
|
export { COMMUNITY_EVENTS, emitEvent } from "./events";
|
|
10
10
|
export type { CommunityEventName } from "./events";
|
|
11
|
-
export { ensureIdentity, resetIdentity, syncProfileFromHost } from "./identity";
|
|
11
|
+
export { ensureIdentity, resetIdentity, resetProfileIdentity, syncProfileFromHost, } from "./identity";
|
|
12
12
|
export { buildFeedSelect, fetchFeedPage, countNewPosts, fetchProfile, fetchUserPosts, searchPosts, fetchThread, createPost, votePoll, createComment, moderateOne, setLike, setReaction, fetchReactionSummaries, reportContent, blockUser, deleteOwnPost, deleteOwnComment, updateProfile, fetchNotificationPreferences, setNotificationPreference, NOTIFICATION_PREFERENCE_KEYS, uploadAvatar, uploadPostImage, POST_IMAGES_BUCKET, } from "./service";
|
|
13
13
|
export type { ModerationVerdict, CreatePostInput, UploadedImage, ReportReason, ReportInput, UpdateProfileInput, UpdateProfileResult, NotificationPreferenceKey, NotificationPreferences, } from "./service";
|
|
14
14
|
export { useCommunityFeed, useSearchPosts, useNewPostsCount, useCommunityUnseenCount, useThread, useProfile, useUserPosts, useMyUid, useNotificationPreferences, useUpdateProfile, useCreatePost, useCreateComment, useToggleLike, useVotePoll, useReactToPost, useReport, useBlockUser, useDeleteContent, isOptimistic, } from "./hooks";
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.REQUIRED_SCHEMA_VERSION = exports.useMarkInboxSeen = exports.useUnreadNotificationCount = exports.useNotificationInbox = exports.unreadCount = exports.markInboxSeen = exports.localizeExcerpts = exports.fetchInbox = exports.isOptimistic = exports.useDeleteContent = exports.useBlockUser = exports.useReport = exports.useReactToPost = exports.useVotePoll = exports.useToggleLike = exports.useCreateComment = exports.useCreatePost = exports.useUpdateProfile = exports.useNotificationPreferences = exports.useMyUid = exports.useUserPosts = exports.useProfile = exports.useThread = exports.useCommunityUnseenCount = exports.useNewPostsCount = exports.useSearchPosts = exports.useCommunityFeed = exports.POST_IMAGES_BUCKET = exports.uploadPostImage = exports.uploadAvatar = exports.NOTIFICATION_PREFERENCE_KEYS = exports.setNotificationPreference = exports.fetchNotificationPreferences = exports.updateProfile = exports.deleteOwnComment = void 0;
|
|
3
|
+
exports.reportContent = exports.fetchReactionSummaries = exports.setReaction = exports.setLike = exports.moderateOne = exports.createComment = exports.votePoll = exports.createPost = exports.fetchThread = exports.searchPosts = exports.fetchUserPosts = exports.fetchProfile = exports.countNewPosts = exports.fetchFeedPage = exports.buildFeedSelect = exports.syncProfileFromHost = exports.resetProfileIdentity = exports.resetIdentity = exports.ensureIdentity = exports.emitEvent = exports.COMMUNITY_EVENTS = exports.timeAgoParts = exports.timeAgo = exports.newestCreatedAt = exports.newPostsLabel = exports.mapCommentRow = exports.applyReaction = exports.applyPollVote = exports.mapPostRow = exports.pollPercent = exports.mapImage = exports.buildPoll = exports.mapProfileRow = exports.normalizeTopic = exports.displayName = exports.EMPTY_REACTION_DATA = exports.EMPTY_POLL_DATA = exports.POLL_OPTION_MAX_LENGTH = exports.POLL_MAX_OPTIONS = exports.POLL_MIN_OPTIONS = exports.COMMENT_MAX_LENGTH = exports.POST_MAX_LENGTH = exports.FEED_PAGE_SIZE = exports.useCommunityConfig = exports.CommunityProvider = exports.languageOf = exports.readerLocale = exports.resolveConfig = exports.DEFAULT_NAME = exports.CommunityDisabledError = void 0;
|
|
4
|
+
exports.REQUIRED_SCHEMA_VERSION = exports.useMarkInboxSeen = exports.useUnreadNotificationCount = exports.useNotificationInbox = exports.unreadCount = exports.markInboxSeen = exports.localizeExcerpts = exports.fetchInbox = exports.isOptimistic = exports.useDeleteContent = exports.useBlockUser = exports.useReport = exports.useReactToPost = exports.useVotePoll = exports.useToggleLike = exports.useCreateComment = exports.useCreatePost = exports.useUpdateProfile = exports.useNotificationPreferences = exports.useMyUid = exports.useUserPosts = exports.useProfile = exports.useThread = exports.useCommunityUnseenCount = exports.useNewPostsCount = exports.useSearchPosts = exports.useCommunityFeed = exports.POST_IMAGES_BUCKET = exports.uploadPostImage = exports.uploadAvatar = exports.NOTIFICATION_PREFERENCE_KEYS = exports.setNotificationPreference = exports.fetchNotificationPreferences = exports.updateProfile = exports.deleteOwnComment = exports.deleteOwnPost = exports.blockUser = void 0;
|
|
5
5
|
var config_1 = require("./config");
|
|
6
6
|
Object.defineProperty(exports, "CommunityDisabledError", { enumerable: true, get: function () { return config_1.CommunityDisabledError; } });
|
|
7
|
+
Object.defineProperty(exports, "DEFAULT_NAME", { enumerable: true, get: function () { return config_1.DEFAULT_NAME; } });
|
|
7
8
|
Object.defineProperty(exports, "resolveConfig", { enumerable: true, get: function () { return config_1.resolveConfig; } });
|
|
8
9
|
var locale_1 = require("./locale");
|
|
9
10
|
Object.defineProperty(exports, "readerLocale", { enumerable: true, get: function () { return locale_1.readerLocale; } });
|
|
@@ -41,6 +42,7 @@ Object.defineProperty(exports, "emitEvent", { enumerable: true, get: function ()
|
|
|
41
42
|
var identity_1 = require("./identity");
|
|
42
43
|
Object.defineProperty(exports, "ensureIdentity", { enumerable: true, get: function () { return identity_1.ensureIdentity; } });
|
|
43
44
|
Object.defineProperty(exports, "resetIdentity", { enumerable: true, get: function () { return identity_1.resetIdentity; } });
|
|
45
|
+
Object.defineProperty(exports, "resetProfileIdentity", { enumerable: true, get: function () { return identity_1.resetProfileIdentity; } });
|
|
44
46
|
Object.defineProperty(exports, "syncProfileFromHost", { enumerable: true, get: function () { return identity_1.syncProfileFromHost; } });
|
|
45
47
|
var service_1 = require("./service");
|
|
46
48
|
Object.defineProperty(exports, "buildFeedSelect", { enumerable: true, get: function () { return service_1.buildFeedSelect; } });
|
package/lib/service.d.ts
CHANGED
|
@@ -157,7 +157,9 @@ export type UpdateProfileResult = {
|
|
|
157
157
|
reason?: string;
|
|
158
158
|
} | {
|
|
159
159
|
status: "error";
|
|
160
|
+
/** `handle_taken` carries a free `suggestion` ("superuser-4821") when one was found. */
|
|
160
161
|
code?: "handle_taken";
|
|
162
|
+
suggestion?: string | null;
|
|
161
163
|
error?: string;
|
|
162
164
|
};
|
|
163
165
|
/**
|
package/package.json
CHANGED