A Swift Package that embeds Pulse chat into an iOS app (DMs, group chats, realtime), extracted from the Pulse iOS app. Auth-agnostic (server-brokered tokens — no Privy) and fully themeable so it matches the host app.
Status: feature-complete v0.1 — models, REST client, realtime socket, auth, and themeable drop-in SwiftUI views (
ConversationsView/ChatView).swift build+swift testpass (Swift 6.3 / Xcode 26). A few request-body field names are marked// NOTE: verifyand should be confirmed against the live API during integration.
.package(url: "https://github.com/MythicalGames/PulseChatKit.git", from: "0.1.0")
// target dependency: .product(name: "PulseChatKit", package: "PulseChatKit")(Or during eval: Xcode → File → Add Package Dependencies → Add Local… → the package folder.)
The host app keeps its own auth. The SDK gets short-lived Pulse session tokens from a provider you supply — your backend mints them with its tenant API key (POST /app/players/{id}/session-token).
import PulseChatKit
let config = PulseChatConfig(
tokenProvider: { await MyBackend.pulseSessionToken() } // your call to your backend
)
let client = PulseChatClient(config: config)
let socket = PulseChatSocket(config: config)Provide a ChatTheme so the screens match your app — no Pulse styling is baked in:
struct PinTapTheme: ChatTheme {
var accentColor: Color { Color("PinTapPink") }
func font(_ w: Font.Weight, _ size: CGFloat) -> Font { .custom("PinTapSans", size: size).weight(w) }
// everything else has sensible defaults you can override
}
// Conversations list -> push a thread
NavigationStack {
ConversationsView(client: client) { convo in
path.append(convo.id)
}
.navigationDestination(for: String.self) { channelID in
ChatView(channelID: channelID, currentUserID: myUserID, client: client, socket: socket)
}
}
.chatTheme(PinTapTheme())ChatView loads history, streams realtime over the socket, sends, and handles reactions. MessageBubble, MessageComposer, ReactionBar, and AvatarView are public too if you want to compose your own screens.
let convos = try await client.getConversations()
let page = try await client.getMessages(channelID: id) // .messages, .nextCursor, .hasMore
let sent = try await client.sendMessage(channelID: id, content: "gg wp")
// images & emoji
let url = try await client.uploadImage(jpegData, filename: "pic.jpg")
_ = try await client.sendMessage(channelID: id, content: url, contentType: "image")
try await client.addReaction(channelID: id, messageID: sent.id, emoji: "🔥")
await socket.connect(); await socket.subscribe(channelID: id)
for await event in socket.events {
if case .messageCreated(let m) = event { /* append */ }
}- Content types supported by the API:
text,image,gif,link; emoji are unicode in text + emoji reactions. - The drop-in views render with SwiftUI only (no UIKit deps) and build for iOS 17+.
- Request-body field names for
createGroupDM/markReadand the image-upload multipart field are marked// NOTE: verify— confirm against the live API during integration.
- Fix: reactions now render.
ChatViewModelhandlesreactionAdded/reactionRemovedsocket events and updates the target message'sreactions(previously these events were dropped, so reactions never appeared). - Fix:
toggleReactionis now optimistic — the pill appears/disappears instantly and rolls back if the request fails. The echoed socket event is idempotent, so no double-counting. - Fix:
ReactionSummarynow has apublic init— host apps can construct/mutate summaries locally instead of refetching the whole channel after every reaction.
- Initial release: themeable drop-in chat UI + headless client/socket, token-provider auth.