@unicitylabs/sphere-sdk 0.6.0 → 0.6.2
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 -0
- package/dist/core/index.cjs +53 -11
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +37 -1
- package/dist/core/index.d.ts +37 -1
- package/dist/core/index.js +53 -11
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +28 -0
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +28 -0
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +28 -0
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +11 -0
- package/dist/impl/nodejs/index.d.ts +11 -0
- package/dist/impl/nodejs/index.js +28 -0
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +53 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +53 -11
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/dist/l1/index.d.cts +0 -717
- package/dist/l1/index.d.ts +0 -717
package/dist/core/index.d.cts
CHANGED
|
@@ -29,6 +29,8 @@ interface GroupData {
|
|
|
29
29
|
unreadCount?: number;
|
|
30
30
|
lastMessageTime?: number;
|
|
31
31
|
lastMessageText?: string;
|
|
32
|
+
/** Only admins and moderators can post; other members are read-only (NIP-29 "write-restricted" tag) */
|
|
33
|
+
writeRestricted?: boolean;
|
|
32
34
|
/** When the current user joined this group locally (used to filter old events) */
|
|
33
35
|
localJoinedAt?: number;
|
|
34
36
|
}
|
|
@@ -61,11 +63,24 @@ interface GroupChatModuleConfig {
|
|
|
61
63
|
/** Max reconnect attempts (default: 5) */
|
|
62
64
|
maxReconnectAttempts?: number;
|
|
63
65
|
}
|
|
66
|
+
interface GroupMessagesPage {
|
|
67
|
+
messages: GroupMessageData[];
|
|
68
|
+
hasMore: boolean;
|
|
69
|
+
oldestTimestamp: number | null;
|
|
70
|
+
}
|
|
71
|
+
interface GetGroupMessagesPageOptions {
|
|
72
|
+
/** Max messages to return (default: 20) */
|
|
73
|
+
limit?: number;
|
|
74
|
+
/** Return messages older than this timestamp */
|
|
75
|
+
before?: number;
|
|
76
|
+
}
|
|
64
77
|
interface CreateGroupOptions {
|
|
65
78
|
name: string;
|
|
66
79
|
description?: string;
|
|
67
80
|
picture?: string;
|
|
68
81
|
visibility?: GroupVisibility;
|
|
82
|
+
/** Only admins and moderators can post; other members are read-only */
|
|
83
|
+
writeRestricted?: boolean;
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
/**
|
|
@@ -758,7 +773,7 @@ interface TrackedAddress extends TrackedAddressEntry {
|
|
|
758
773
|
/** Primary nametag (from nametag cache, without @ prefix) */
|
|
759
774
|
readonly nametag?: string;
|
|
760
775
|
}
|
|
761
|
-
type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'payment_request:incoming' | 'payment_request:accepted' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:response' | 'message:dm' | 'message:read' | 'message:typing' | 'composing:started' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:provider' | 'sync:error' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden' | 'sync:remote-update' | 'groupchat:message' | 'groupchat:joined' | 'groupchat:left' | 'groupchat:kicked' | 'groupchat:group_deleted' | 'groupchat:updated' | 'groupchat:connection' | 'history:updated';
|
|
776
|
+
type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'payment_request:incoming' | 'payment_request:accepted' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:response' | 'message:dm' | 'message:read' | 'message:typing' | 'composing:started' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:provider' | 'sync:error' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden' | 'sync:remote-update' | 'groupchat:message' | 'groupchat:joined' | 'groupchat:left' | 'groupchat:kicked' | 'groupchat:group_deleted' | 'groupchat:updated' | 'groupchat:connection' | 'groupchat:ready' | 'communications:ready' | 'history:updated';
|
|
762
777
|
interface SphereEventMap {
|
|
763
778
|
'transfer:incoming': IncomingTransfer;
|
|
764
779
|
'transfer:confirmed': TransferResult;
|
|
@@ -858,6 +873,12 @@ interface SphereEventMap {
|
|
|
858
873
|
'groupchat:connection': {
|
|
859
874
|
connected: boolean;
|
|
860
875
|
};
|
|
876
|
+
'groupchat:ready': {
|
|
877
|
+
groupCount: number;
|
|
878
|
+
};
|
|
879
|
+
'communications:ready': {
|
|
880
|
+
conversationCount: number;
|
|
881
|
+
};
|
|
861
882
|
'history:updated': TransactionHistoryEntry;
|
|
862
883
|
}
|
|
863
884
|
type SphereEventHandler<T extends SphereEventType> = (data: SphereEventMap[T]) => void;
|
|
@@ -1385,6 +1406,14 @@ interface TransportProvider extends BaseProvider {
|
|
|
1385
1406
|
* and resolves after EOSE (End Of Stored Events).
|
|
1386
1407
|
*/
|
|
1387
1408
|
fetchPendingEvents?(): Promise<void>;
|
|
1409
|
+
/**
|
|
1410
|
+
* Register a handler to be called when the chat subscription receives EOSE
|
|
1411
|
+
* (End Of Stored Events), indicating that historical DMs have been delivered.
|
|
1412
|
+
* The handler fires at most once per subscription lifecycle.
|
|
1413
|
+
*
|
|
1414
|
+
* @returns Unsubscribe function
|
|
1415
|
+
*/
|
|
1416
|
+
onChatReady?(handler: () => void): () => void;
|
|
1388
1417
|
}
|
|
1389
1418
|
/**
|
|
1390
1419
|
* Payload for sending instant split bundles
|
|
@@ -2947,6 +2976,7 @@ declare class GroupChatModule {
|
|
|
2947
2976
|
getGroups(): GroupData[];
|
|
2948
2977
|
getGroup(groupId: string): GroupData | null;
|
|
2949
2978
|
getMessages(groupId: string): GroupMessageData[];
|
|
2979
|
+
getMessagesPage(groupId: string, options?: GetGroupMessagesPageOptions): GroupMessagesPage;
|
|
2950
2980
|
getMembers(groupId: string): GroupMemberData[];
|
|
2951
2981
|
getMember(groupId: string, pubkey: string): GroupMemberData | null;
|
|
2952
2982
|
getTotalUnreadCount(): number;
|
|
@@ -2962,6 +2992,12 @@ declare class GroupChatModule {
|
|
|
2962
2992
|
*/
|
|
2963
2993
|
canModerateGroup(groupId: string): Promise<boolean>;
|
|
2964
2994
|
isCurrentUserRelayAdmin(): Promise<boolean>;
|
|
2995
|
+
/**
|
|
2996
|
+
* Check if current user can write messages to a group.
|
|
2997
|
+
* For write-restricted groups, only admins/moderators can post.
|
|
2998
|
+
* For normal groups, any member can post.
|
|
2999
|
+
*/
|
|
3000
|
+
canWriteToGroup(groupId: string): boolean;
|
|
2965
3001
|
getCurrentUserRole(groupId: string): GroupRole | null;
|
|
2966
3002
|
onMessage(handler: (message: GroupMessageData) => void): () => void;
|
|
2967
3003
|
getRelayUrls(): string[];
|
package/dist/core/index.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ interface GroupData {
|
|
|
29
29
|
unreadCount?: number;
|
|
30
30
|
lastMessageTime?: number;
|
|
31
31
|
lastMessageText?: string;
|
|
32
|
+
/** Only admins and moderators can post; other members are read-only (NIP-29 "write-restricted" tag) */
|
|
33
|
+
writeRestricted?: boolean;
|
|
32
34
|
/** When the current user joined this group locally (used to filter old events) */
|
|
33
35
|
localJoinedAt?: number;
|
|
34
36
|
}
|
|
@@ -61,11 +63,24 @@ interface GroupChatModuleConfig {
|
|
|
61
63
|
/** Max reconnect attempts (default: 5) */
|
|
62
64
|
maxReconnectAttempts?: number;
|
|
63
65
|
}
|
|
66
|
+
interface GroupMessagesPage {
|
|
67
|
+
messages: GroupMessageData[];
|
|
68
|
+
hasMore: boolean;
|
|
69
|
+
oldestTimestamp: number | null;
|
|
70
|
+
}
|
|
71
|
+
interface GetGroupMessagesPageOptions {
|
|
72
|
+
/** Max messages to return (default: 20) */
|
|
73
|
+
limit?: number;
|
|
74
|
+
/** Return messages older than this timestamp */
|
|
75
|
+
before?: number;
|
|
76
|
+
}
|
|
64
77
|
interface CreateGroupOptions {
|
|
65
78
|
name: string;
|
|
66
79
|
description?: string;
|
|
67
80
|
picture?: string;
|
|
68
81
|
visibility?: GroupVisibility;
|
|
82
|
+
/** Only admins and moderators can post; other members are read-only */
|
|
83
|
+
writeRestricted?: boolean;
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
/**
|
|
@@ -758,7 +773,7 @@ interface TrackedAddress extends TrackedAddressEntry {
|
|
|
758
773
|
/** Primary nametag (from nametag cache, without @ prefix) */
|
|
759
774
|
readonly nametag?: string;
|
|
760
775
|
}
|
|
761
|
-
type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'payment_request:incoming' | 'payment_request:accepted' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:response' | 'message:dm' | 'message:read' | 'message:typing' | 'composing:started' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:provider' | 'sync:error' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden' | 'sync:remote-update' | 'groupchat:message' | 'groupchat:joined' | 'groupchat:left' | 'groupchat:kicked' | 'groupchat:group_deleted' | 'groupchat:updated' | 'groupchat:connection' | 'history:updated';
|
|
776
|
+
type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'payment_request:incoming' | 'payment_request:accepted' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:response' | 'message:dm' | 'message:read' | 'message:typing' | 'composing:started' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:provider' | 'sync:error' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden' | 'sync:remote-update' | 'groupchat:message' | 'groupchat:joined' | 'groupchat:left' | 'groupchat:kicked' | 'groupchat:group_deleted' | 'groupchat:updated' | 'groupchat:connection' | 'groupchat:ready' | 'communications:ready' | 'history:updated';
|
|
762
777
|
interface SphereEventMap {
|
|
763
778
|
'transfer:incoming': IncomingTransfer;
|
|
764
779
|
'transfer:confirmed': TransferResult;
|
|
@@ -858,6 +873,12 @@ interface SphereEventMap {
|
|
|
858
873
|
'groupchat:connection': {
|
|
859
874
|
connected: boolean;
|
|
860
875
|
};
|
|
876
|
+
'groupchat:ready': {
|
|
877
|
+
groupCount: number;
|
|
878
|
+
};
|
|
879
|
+
'communications:ready': {
|
|
880
|
+
conversationCount: number;
|
|
881
|
+
};
|
|
861
882
|
'history:updated': TransactionHistoryEntry;
|
|
862
883
|
}
|
|
863
884
|
type SphereEventHandler<T extends SphereEventType> = (data: SphereEventMap[T]) => void;
|
|
@@ -1385,6 +1406,14 @@ interface TransportProvider extends BaseProvider {
|
|
|
1385
1406
|
* and resolves after EOSE (End Of Stored Events).
|
|
1386
1407
|
*/
|
|
1387
1408
|
fetchPendingEvents?(): Promise<void>;
|
|
1409
|
+
/**
|
|
1410
|
+
* Register a handler to be called when the chat subscription receives EOSE
|
|
1411
|
+
* (End Of Stored Events), indicating that historical DMs have been delivered.
|
|
1412
|
+
* The handler fires at most once per subscription lifecycle.
|
|
1413
|
+
*
|
|
1414
|
+
* @returns Unsubscribe function
|
|
1415
|
+
*/
|
|
1416
|
+
onChatReady?(handler: () => void): () => void;
|
|
1388
1417
|
}
|
|
1389
1418
|
/**
|
|
1390
1419
|
* Payload for sending instant split bundles
|
|
@@ -2947,6 +2976,7 @@ declare class GroupChatModule {
|
|
|
2947
2976
|
getGroups(): GroupData[];
|
|
2948
2977
|
getGroup(groupId: string): GroupData | null;
|
|
2949
2978
|
getMessages(groupId: string): GroupMessageData[];
|
|
2979
|
+
getMessagesPage(groupId: string, options?: GetGroupMessagesPageOptions): GroupMessagesPage;
|
|
2950
2980
|
getMembers(groupId: string): GroupMemberData[];
|
|
2951
2981
|
getMember(groupId: string, pubkey: string): GroupMemberData | null;
|
|
2952
2982
|
getTotalUnreadCount(): number;
|
|
@@ -2962,6 +2992,12 @@ declare class GroupChatModule {
|
|
|
2962
2992
|
*/
|
|
2963
2993
|
canModerateGroup(groupId: string): Promise<boolean>;
|
|
2964
2994
|
isCurrentUserRelayAdmin(): Promise<boolean>;
|
|
2995
|
+
/**
|
|
2996
|
+
* Check if current user can write messages to a group.
|
|
2997
|
+
* For write-restricted groups, only admins/moderators can post.
|
|
2998
|
+
* For normal groups, any member can post.
|
|
2999
|
+
*/
|
|
3000
|
+
canWriteToGroup(groupId: string): boolean;
|
|
2965
3001
|
getCurrentUserRole(groupId: string): GroupRole | null;
|
|
2966
3002
|
onMessage(handler: (message: GroupMessageData) => void): () => void;
|
|
2967
3003
|
getRelayUrls(): string[];
|
package/dist/core/index.js
CHANGED
|
@@ -8299,6 +8299,12 @@ var CommunicationsModule = class {
|
|
|
8299
8299
|
this.unsubscribeComposing = deps.transport.onComposing?.((indicator) => {
|
|
8300
8300
|
this.handleComposingIndicator(indicator);
|
|
8301
8301
|
}) ?? null;
|
|
8302
|
+
if (deps.transport.onChatReady) {
|
|
8303
|
+
deps.transport.onChatReady(() => {
|
|
8304
|
+
const conversations = this.getConversations();
|
|
8305
|
+
deps.emitEvent("communications:ready", { conversationCount: conversations.size });
|
|
8306
|
+
});
|
|
8307
|
+
}
|
|
8302
8308
|
}
|
|
8303
8309
|
/**
|
|
8304
8310
|
* Load messages from storage.
|
|
@@ -8945,6 +8951,7 @@ var GroupChatModule = class {
|
|
|
8945
8951
|
await this.subscribeToJoinedGroups();
|
|
8946
8952
|
}
|
|
8947
8953
|
this.deps.emitEvent("groupchat:connection", { connected: true });
|
|
8954
|
+
this.deps.emitEvent("groupchat:ready", { groupCount: this.groups.size });
|
|
8948
8955
|
} catch (error) {
|
|
8949
8956
|
logger.error("GroupChat", "Failed to connect to relays", error);
|
|
8950
8957
|
this.deps.emitEvent("groupchat:connection", { connected: false });
|
|
@@ -9063,17 +9070,23 @@ var GroupChatModule = class {
|
|
|
9063
9070
|
const group = this.groups.get(groupId);
|
|
9064
9071
|
if (!group) return;
|
|
9065
9072
|
if (event.kind === NIP29_KINDS.GROUP_METADATA) {
|
|
9066
|
-
if (
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9073
|
+
if (event.content && event.content.trim()) {
|
|
9074
|
+
try {
|
|
9075
|
+
const metadata = JSON.parse(event.content);
|
|
9076
|
+
group.name = metadata.name || group.name;
|
|
9077
|
+
group.description = metadata.about || group.description;
|
|
9078
|
+
group.picture = metadata.picture || group.picture;
|
|
9079
|
+
if (metadata["write-restricted"] === true) group.writeRestricted = true;
|
|
9080
|
+
else group.writeRestricted = void 0;
|
|
9081
|
+
} catch {
|
|
9082
|
+
}
|
|
9076
9083
|
}
|
|
9084
|
+
for (const tag of event.tags) {
|
|
9085
|
+
if (tag[0] === "write-restricted") group.writeRestricted = true;
|
|
9086
|
+
}
|
|
9087
|
+
group.updatedAt = event.created_at * 1e3;
|
|
9088
|
+
this.groups.set(groupId, group);
|
|
9089
|
+
this.persistGroups();
|
|
9077
9090
|
} else if (event.kind === NIP29_KINDS.GROUP_MEMBERS) {
|
|
9078
9091
|
this.updateMembersFromEvent(groupId, event);
|
|
9079
9092
|
} else if (event.kind === NIP29_KINDS.GROUP_ADMINS) {
|
|
@@ -9377,7 +9390,8 @@ var GroupChatModule = class {
|
|
|
9377
9390
|
picture: options.picture,
|
|
9378
9391
|
closed: true,
|
|
9379
9392
|
private: isPrivate,
|
|
9380
|
-
hidden: isPrivate
|
|
9393
|
+
hidden: isPrivate,
|
|
9394
|
+
...options.writeRestricted ? { "write-restricted": true } : {}
|
|
9381
9395
|
})
|
|
9382
9396
|
});
|
|
9383
9397
|
if (!eventId) return null;
|
|
@@ -9572,6 +9586,19 @@ var GroupChatModule = class {
|
|
|
9572
9586
|
getMessages(groupId) {
|
|
9573
9587
|
return (this.messages.get(groupId) || []).sort((a, b) => a.timestamp - b.timestamp);
|
|
9574
9588
|
}
|
|
9589
|
+
getMessagesPage(groupId, options) {
|
|
9590
|
+
const limit = options?.limit ?? 20;
|
|
9591
|
+
const before = options?.before ?? Infinity;
|
|
9592
|
+
const groupMessages = this.messages.get(groupId) ?? [];
|
|
9593
|
+
const filtered = groupMessages.filter((m) => m.timestamp < before).sort((a, b) => b.timestamp - a.timestamp);
|
|
9594
|
+
const page = filtered.slice(0, limit);
|
|
9595
|
+
return {
|
|
9596
|
+
messages: page.reverse(),
|
|
9597
|
+
// chronological order
|
|
9598
|
+
hasMore: filtered.length > limit,
|
|
9599
|
+
oldestTimestamp: page.length > 0 ? page[0].timestamp : null
|
|
9600
|
+
};
|
|
9601
|
+
}
|
|
9575
9602
|
getMembers(groupId) {
|
|
9576
9603
|
return (this.members.get(groupId) || []).sort((a, b) => a.joinedAt - b.joinedAt);
|
|
9577
9604
|
}
|
|
@@ -9678,6 +9705,17 @@ var GroupChatModule = class {
|
|
|
9678
9705
|
const admins = await this.fetchRelayAdmins();
|
|
9679
9706
|
return admins.has(myPubkey);
|
|
9680
9707
|
}
|
|
9708
|
+
/**
|
|
9709
|
+
* Check if current user can write messages to a group.
|
|
9710
|
+
* For write-restricted groups, only admins/moderators can post.
|
|
9711
|
+
* For normal groups, any member can post.
|
|
9712
|
+
*/
|
|
9713
|
+
canWriteToGroup(groupId) {
|
|
9714
|
+
const group = this.groups.get(groupId);
|
|
9715
|
+
if (!group) return false;
|
|
9716
|
+
if (!group.writeRestricted) return true;
|
|
9717
|
+
return this.isCurrentUserModerator(groupId);
|
|
9718
|
+
}
|
|
9681
9719
|
getCurrentUserRole(groupId) {
|
|
9682
9720
|
const myPubkey = this.getMyPublicKey();
|
|
9683
9721
|
if (!myPubkey) return null;
|
|
@@ -10052,6 +10090,7 @@ var GroupChatModule = class {
|
|
|
10052
10090
|
let description;
|
|
10053
10091
|
let picture;
|
|
10054
10092
|
let isPrivate = false;
|
|
10093
|
+
let writeRestricted = false;
|
|
10055
10094
|
if (event.content && event.content.trim()) {
|
|
10056
10095
|
try {
|
|
10057
10096
|
const metadata = JSON.parse(event.content);
|
|
@@ -10059,6 +10098,7 @@ var GroupChatModule = class {
|
|
|
10059
10098
|
description = metadata.about || metadata.description;
|
|
10060
10099
|
picture = metadata.picture;
|
|
10061
10100
|
isPrivate = metadata.private === true;
|
|
10101
|
+
if (metadata["write-restricted"] === true) writeRestricted = true;
|
|
10062
10102
|
} catch {
|
|
10063
10103
|
}
|
|
10064
10104
|
}
|
|
@@ -10068,6 +10108,7 @@ var GroupChatModule = class {
|
|
|
10068
10108
|
if (tag[0] === "picture" && tag[1]) picture = tag[1];
|
|
10069
10109
|
if (tag[0] === "private") isPrivate = true;
|
|
10070
10110
|
if (tag[0] === "public" && tag[1] === "false") isPrivate = true;
|
|
10111
|
+
if (tag[0] === "write-restricted") writeRestricted = true;
|
|
10071
10112
|
}
|
|
10072
10113
|
return {
|
|
10073
10114
|
id: groupId,
|
|
@@ -10076,6 +10117,7 @@ var GroupChatModule = class {
|
|
|
10076
10117
|
description,
|
|
10077
10118
|
picture,
|
|
10078
10119
|
visibility: isPrivate ? GroupVisibility.PRIVATE : GroupVisibility.PUBLIC,
|
|
10120
|
+
writeRestricted: writeRestricted || void 0,
|
|
10079
10121
|
createdAt: event.created_at * 1e3
|
|
10080
10122
|
};
|
|
10081
10123
|
} catch {
|