@candypoets/nipworker 0.96.3 → 0.96.5

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.
Files changed (180) hide show
  1. package/README.md +206 -73
  2. package/crates/native-ffi/README.md +14 -151
  3. package/crates/native-ffi/ios/README.md +4 -50
  4. package/crates/native-ffi/react-native/README.md +8 -7
  5. package/crates/native-ffi/react-native/android/build.gradle +1 -0
  6. package/crates/native-ffi/react-native/android/src/main/cpp/NipworkerByteRuntime.cpp +348 -0
  7. package/crates/native-ffi/react-native/android/src/main/java/com/candypoets/nipworker/reactnative/NipworkerHooks.kt +292 -0
  8. package/crates/native-ffi/react-native/android/src/main/java/com/candypoets/nipworker/reactnative/NipworkerReactNativeModule.kt +283 -89
  9. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/AddressPointer.java +75 -0
  10. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ArticleBlock.java +102 -0
  11. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ArticleBlockType.java +23 -0
  12. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ArticleEntity.java +80 -0
  13. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ArticleInline.java +85 -0
  14. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ArticleInlineType.java +24 -0
  15. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/BadgeAwardRecipient.java +61 -0
  16. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/BufferFull.java +52 -0
  17. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CacheInput.java +16 -0
  18. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CacheInputMessage.java +57 -0
  19. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CacheRequest.java +84 -0
  20. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CacheResponse.java +67 -0
  21. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CashuData.java +55 -0
  22. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ChatLimiterPipeConfig.java +74 -0
  23. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CodeData.java +61 -0
  24. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ConnectionStatus.java +68 -0
  25. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Contact.java +70 -0
  26. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ContentBlock.java +70 -0
  27. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ContentData.java +23 -0
  28. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Coordinate.java +75 -0
  29. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CountResponse.java +60 -0
  30. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/CounterPipeConfig.java +67 -0
  31. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/DLEQProof.java +69 -0
  32. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Emoji.java +62 -0
  33. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/EmojiData.java +68 -0
  34. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Eoce.java +55 -0
  35. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/EventPointer.java +74 -0
  36. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/GetPublicKey.java +43 -0
  37. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/HTLCWitness.java +64 -0
  38. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/HashtagData.java +55 -0
  39. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/HistoryTag.java +74 -0
  40. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ImageData.java +67 -0
  41. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ImetaTag.java +100 -0
  42. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind0Parsed.java +168 -0
  43. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind10002Parsed.java +59 -0
  44. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind10019Parsed.java +73 -0
  45. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind1018Parsed.java +79 -0
  46. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind1068Parsed.java +107 -0
  47. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind1111Parsed.java +159 -0
  48. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind1311Parsed.java +91 -0
  49. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind17375Parsed.java +74 -0
  50. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind17Parsed.java +81 -0
  51. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind1Parsed.java +109 -0
  52. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind20Parsed.java +107 -0
  53. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind22Parsed.java +111 -0
  54. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind30023Parsed.java +113 -0
  55. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind3Parsed.java +59 -0
  56. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind4Parsed.java +78 -0
  57. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind6Parsed.java +53 -0
  58. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind7374Parsed.java +66 -0
  59. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind7375Parsed.java +79 -0
  60. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind7376Parsed.java +100 -0
  61. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind7Parsed.java +81 -0
  62. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind8Parsed.java +78 -0
  63. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind9321Parsed.java +117 -0
  64. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Kind9735Parsed.java +119 -0
  65. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/KindFilterPipeConfig.java +60 -0
  66. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/LinkPreviewData.java +73 -0
  67. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ListParsed.java +123 -0
  68. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/LiveActivityRef.java +72 -0
  69. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/LiveChatParticipant.java +61 -0
  70. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/LiveChatThreadRef.java +61 -0
  71. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MainContent.java +20 -0
  72. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MainMessage.java +57 -0
  73. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MediaGroupData.java +58 -0
  74. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MediaItem.java +58 -0
  75. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Message.java +24 -0
  76. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MessageType.java +19 -0
  77. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MintInfo.java +65 -0
  78. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MintProofs.java +66 -0
  79. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/MuteFilterPipeConfig.java +84 -0
  80. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Nip07.java +43 -0
  81. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Nip46Bunker.java +61 -0
  82. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Nip46QR.java +61 -0
  83. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/NostrData.java +81 -0
  84. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/NostrEvent.java +95 -0
  85. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/NpubLimiterKey.java +15 -0
  86. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/NpubLimiterPipeConfig.java +64 -0
  87. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/P2PKWitness.java +57 -0
  88. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ParsePipeConfig.java +43 -0
  89. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ParsedData.java +39 -0
  90. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ParsedDataUnion.java +35 -0
  91. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ParsedEvent.java +108 -0
  92. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Pipe.java +57 -0
  93. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PipeConfig.java +23 -0
  94. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PipelineConfig.java +59 -0
  95. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PollOption.java +62 -0
  96. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PollType.java +15 -0
  97. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PreGenericParsed.java +213 -0
  98. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PreParticipant.java +73 -0
  99. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PreRefEvent.java +67 -0
  100. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/PrivateKey.java +55 -0
  101. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ProfilePointer.java +64 -0
  102. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Proof.java +90 -0
  103. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ProofVerificationPipeConfig.java +52 -0
  104. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Pubkey.java +55 -0
  105. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Publish.java +80 -0
  106. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Raw.java +55 -0
  107. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ReactionType.java +17 -0
  108. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/RelayInfo.java +63 -0
  109. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Request.java +134 -0
  110. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SaveToDbPipeConfig.java +43 -0
  111. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SerializeEventsPipeConfig.java +55 -0
  112. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SetSigner.java +57 -0
  113. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignEvent.java +54 -0
  114. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignedEvent.java +54 -0
  115. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignerOp.java +19 -0
  116. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignerRequest.java +80 -0
  117. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignerResponse.java +64 -0
  118. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SignerType.java +18 -0
  119. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/StringVec.java +57 -0
  120. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Subscribe.java +72 -0
  121. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SubscribeKind.java +19 -0
  122. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/SubscriptionConfig.java +95 -0
  123. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Tag.java +64 -0
  124. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Template.java +74 -0
  125. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Unsubscribe.java +55 -0
  126. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ValidProofs.java +59 -0
  127. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/VideoData.java +67 -0
  128. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/VideoVariant.java +102 -0
  129. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/Witness.java +17 -0
  130. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/WitnessString.java +54 -0
  131. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/WorkerMessage.java +74 -0
  132. package/crates/native-ffi/react-native/android/src/main/java/nostr/fb/ZapRequest.java +83 -0
  133. package/crates/native-ffi/react-native/ios/NipworkerReactNativeModule.h +18 -0
  134. package/crates/native-ffi/react-native/ios/NipworkerReactNativeModule.mm +263 -8
  135. package/dist/EngineManager.d.ts +1 -0
  136. package/dist/EngineManager.d.ts.map +1 -1
  137. package/dist/EngineManager.js +143 -137
  138. package/dist/NostrManager.d.ts +1 -0
  139. package/dist/NostrManager.d.ts.map +1 -1
  140. package/dist/NostrManager.js +49 -44
  141. package/dist/connections/index.js +87 -87
  142. package/dist/engine/index.js +119 -119
  143. package/dist/engine.d.ts.map +1 -1
  144. package/dist/hooks.d.ts +1 -1
  145. package/dist/hooks.d.ts.map +1 -1
  146. package/dist/hooks.js +27 -26
  147. package/dist/index.d.ts +1 -1
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/legacy.d.ts.map +1 -1
  150. package/dist/lib/ArrayBufferReader.d.ts +2 -1
  151. package/dist/lib/ArrayBufferReader.d.ts.map +1 -1
  152. package/dist/lib/BaseBackend.d.ts +5 -3
  153. package/dist/lib/BaseBackend.d.ts.map +1 -1
  154. package/dist/manager.d.ts +2 -6
  155. package/dist/manager.d.ts.map +1 -1
  156. package/dist/manager.js +10 -28
  157. package/dist/react-native.d.ts +33 -4
  158. package/dist/react-native.d.ts.map +1 -1
  159. package/dist/react-native.js +400 -122
  160. package/dist/scheduleMicrotask.js +58 -50
  161. package/package.json +13 -11
  162. package/swift/Package.swift +36 -0
  163. package/swift/README.md +438 -0
  164. package/swift/Sources/NipworkerSwift/ArrayBufferReader.swift +136 -0
  165. package/swift/Sources/NipworkerSwift/AuthSession.swift +24 -0
  166. package/swift/Sources/NipworkerSwift/FFI.swift +94 -0
  167. package/swift/Sources/NipworkerSwift/FlatBufferHelpers.swift +273 -0
  168. package/swift/Sources/NipworkerSwift/Generated/message_generated.swift +6530 -0
  169. package/swift/Sources/NipworkerSwift/Hooks.swift +300 -0
  170. package/swift/Sources/NipworkerSwift/Models.swift +156 -0
  171. package/swift/Sources/NipworkerSwift/NostrManager.swift +594 -0
  172. package/swift/Sources/NipworkerSwift/PublishTracker.swift +40 -0
  173. package/swift/Sources/NipworkerSwift/Subscription.swift +44 -0
  174. package/swift/Sources/NipworkerSwift/WorkerMessageView.swift +145 -0
  175. package/dist/NativeBackend.d.ts +0 -53
  176. package/dist/NativeBackend.d.ts.map +0 -1
  177. package/dist/NativeBackend.js +0 -464
  178. package/dist/native.d.ts +0 -29
  179. package/dist/native.d.ts.map +0 -1
  180. package/dist/native.js +0 -273
@@ -0,0 +1,438 @@
1
+ # NipworkerSwift
2
+
3
+ Swift bindings for NIPWorker — a high-performance Nostr client library powered by Rust WebAssembly and FlatBuffers. This package wraps the Rust native FFI layer to provide an async-first, actor-safe Swift API for subscribing to relays, publishing events, and managing cryptographic signers.
4
+
5
+ ## Requirements
6
+
7
+ - **iOS 14+** / **macOS 13+**
8
+ - **Swift 5.9+**
9
+ - **Xcode 15+** (recommended)
10
+
11
+ ## Installation
12
+
13
+ ### Xcode — Local Package (recommended while iterating)
14
+
15
+ 1. Open your iOS/macOS project in Xcode.
16
+ 2. Go to **File → Add Package Dependencies…**
17
+ 3. Click **Add Local…** and select the `swift/` folder inside this repository.
18
+ 4. Add `NipworkerSwift` to your app target.
19
+
20
+ > **Note on the XCFramework path**: `Package.swift` references the XCFramework via a relative path (`../crates/native-ffi/ios/`). Some Xcode/SPM versions reject binary targets that live outside the package root. If you hit this, copy or symlink the framework into the package:
21
+ >
22
+ > ```bash
23
+ > mkdir -p swift/Frameworks
24
+ > cp -R crates/native-ffi/ios/NipworkerNativeFFI.xcframework swift/Frameworks/
25
+ > ```
26
+ >
27
+ > Then change the `binaryTarget` path in `swift/Package.swift` to `"Frameworks/NipworkerNativeFFI.xcframework"`.
28
+
29
+ ### Swift Package Manager
30
+
31
+ Add the following to your `Package.swift`:
32
+
33
+ ```swift
34
+ // swift-tools-version:5.9
35
+ import PackageDescription
36
+
37
+ let package = Package(
38
+ name: "YourApp",
39
+ platforms: [.iOS(.v14), .macOS(.v13)],
40
+ dependencies: [
41
+ .package(name: "NipworkerSwift", path: "../path-to-nipworker-repo/swift")
42
+ ],
43
+ targets: [
44
+ .executableTarget(
45
+ name: "YourApp",
46
+ dependencies: ["NipworkerSwift"]
47
+ )
48
+ ]
49
+ )
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ```swift
55
+ import NipworkerSwift
56
+
57
+ let manager = NostrManager()
58
+
59
+ let request = RequestObject(
60
+ filters: [.init(kinds: [1], limit: 10)],
61
+ relays: ["wss://relay.damus.io"]
62
+ )
63
+
64
+ let buffer = await manager.subscribe(
65
+ subscriptionId: "feed",
66
+ requests: [request]
67
+ )
68
+
69
+ let unsub = useSubscription(
70
+ manager: manager,
71
+ subscriptionId: "feed",
72
+ requests: [request]
73
+ ) { events in
74
+ for event in events {
75
+ print("Event:", event.id, event.content)
76
+ }
77
+ }
78
+ ```
79
+
80
+ ## API Reference
81
+
82
+ ### `NostrManager`
83
+
84
+ The core actor that owns the Rust engine handle. All methods are `async` or return values synchronously from the actor's isolated state.
85
+
86
+ ```swift
87
+ public actor NostrManager {
88
+ public init(config: NostrManagerConfig = NostrManagerConfig())
89
+
90
+ // Subscription
91
+ public func subscribe(
92
+ subscriptionId: String,
93
+ requests: [RequestObject],
94
+ options: SubscriptionConfig = SubscriptionConfig()
95
+ ) -> SubscriptionBuffer
96
+
97
+ public func unsubscribe(subscriptionId: String)
98
+
99
+ // Publish
100
+ public func publish(
101
+ publishId: String,
102
+ event: NostrEvent,
103
+ defaultRelays: [String] = [],
104
+ optimisticSubIds: [String] = []
105
+ ) -> SubscriptionBuffer
106
+
107
+ // Signer
108
+ public func setSigner(_ signer: SignerTypeName)
109
+ public func getPublicKey() async throws -> String
110
+ public func signEvent(_ template: EventTemplate) async throws -> NostrEvent
111
+ public func logout()
112
+
113
+ // Read helpers
114
+ public func readEvents(for subId: String, from position: Int) -> (events: [NostrEvent], newPosition: Int)
115
+ public func readPublishStatuses(for publishId: String, from position: Int) -> (statuses: [String: PublishStatus], newPosition: Int)
116
+ public func getRelayStatuses() -> [String: RelayStatus]
117
+ public func getActivePubkey() -> String?
118
+
119
+ public func cleanup()
120
+ }
121
+ ```
122
+
123
+ ### Models
124
+
125
+ #### `RequestObject`
126
+
127
+ Defines a Nostr filter + relay set.
128
+
129
+ ```swift
130
+ public struct RequestObject {
131
+ public var ids: [String]?
132
+ public var authors: [String]?
133
+ public var kinds: [UInt16]?
134
+ public var tags: [String: [String]]?
135
+ public var since: Int?
136
+ public var until: Int?
137
+ public var limit: Int?
138
+ public var search: String?
139
+ public var relays: [String]
140
+ public var closeOnEOSE: Bool?
141
+ public var cacheFirst: Bool?
142
+ public var noCache: Bool?
143
+ public var maxRelays: UInt16?
144
+ }
145
+ ```
146
+
147
+ #### `SubscriptionConfig`
148
+
149
+ Controls subscription behavior.
150
+
151
+ ```swift
152
+ public struct SubscriptionConfig {
153
+ public var pipeline: [PipeConfig]?
154
+ public var closeOnEose: Bool
155
+ public var cacheFirst: Bool
156
+ public var timeoutMs: UInt64?
157
+ public var maxEvents: UInt32?
158
+ public var skipCache: Bool
159
+ public var force: Bool
160
+ public var bytesPerEvent: UInt32
161
+ public var isSlow: Bool
162
+ public var pagination: String?
163
+ public var cacheOnly: Bool
164
+ }
165
+ ```
166
+
167
+ #### `NostrEvent`
168
+
169
+ ```swift
170
+ public struct NostrEvent: Identifiable, Equatable, Sendable {
171
+ public var id: String
172
+ public var pubkey: String
173
+ public var kind: UInt16
174
+ public var content: String
175
+ public var tags: [[String]]
176
+ public var createdAt: Int
177
+ public var sig: String
178
+ }
179
+ ```
180
+
181
+ #### `EventTemplate`
182
+
183
+ Used when signing a new event.
184
+
185
+ ```swift
186
+ public struct EventTemplate {
187
+ public var kind: UInt16
188
+ public var content: String
189
+ public var tags: [[String]]
190
+ }
191
+ ```
192
+
193
+ #### `SignerTypeName`
194
+
195
+ ```swift
196
+ public enum SignerTypeName: Sendable {
197
+ case pubkey(String)
198
+ case privkey(String)
199
+ }
200
+ ```
201
+
202
+ #### `RelayStatus`
203
+
204
+ ```swift
205
+ public enum RelayStatus: Equatable, Sendable {
206
+ case connected
207
+ case failed
208
+ case closed(String?)
209
+ }
210
+ ```
211
+
212
+ #### `PublishStatus`
213
+
214
+ ```swift
215
+ public enum PublishStatus: Equatable, Sendable {
216
+ case pending
217
+ case success(String)
218
+ case failed(String)
219
+ }
220
+ ```
221
+
222
+ ### Hooks
223
+
224
+ Callback-based helpers that mirror the JS `useSubscription` / `usePublish` / `useRelayStatus` hooks. They return an unsubscribe function.
225
+
226
+ ```swift
227
+ public func useSubscription(
228
+ manager: NostrManager,
229
+ subscriptionId: String,
230
+ requests: [RequestObject],
231
+ callback: @escaping ([NostrEvent]) -> Void,
232
+ options: SubscriptionConfig = SubscriptionConfig()
233
+ ) -> () -> Void
234
+
235
+ public func usePublish(
236
+ manager: NostrManager,
237
+ publishId: String,
238
+ event: NostrEvent,
239
+ callback: @escaping ([String: PublishStatus]) -> Void,
240
+ defaultRelays: [String] = [],
241
+ optimisticSubIds: [String] = []
242
+ ) -> () -> Void
243
+
244
+ public func useRelayStatus(
245
+ manager: NostrManager,
246
+ onStatus: @escaping (String, RelayStatus) -> Void
247
+ ) -> () -> Void
248
+ ```
249
+
250
+ ### SwiftUI Helpers
251
+
252
+ #### `Subscription`
253
+
254
+ An `@MainActor ObservableObject` that wraps a subscription and exposes `@Published` events.
255
+
256
+ ```swift
257
+ @MainActor
258
+ public final class Subscription: ObservableObject {
259
+ @Published public private(set) var events: [NostrEvent] = []
260
+ @Published public private(set) var isComplete: Bool = false
261
+ public let id: String
262
+
263
+ public init(id: String, manager: NostrManager)
264
+ }
265
+ ```
266
+
267
+ #### `PublishTracker`
268
+
269
+ An `@MainActor ObservableObject` that tracks publish statuses.
270
+
271
+ ```swift
272
+ @MainActor
273
+ public final class PublishTracker: ObservableObject {
274
+ @Published public private(set) var statuses: [String: PublishStatus] = [:]
275
+ public let publishId: String
276
+
277
+ public init(publishId: String, manager: NostrManager)
278
+ }
279
+ ```
280
+
281
+ ## Examples
282
+
283
+ ### Subscribe & display events in SwiftUI
284
+
285
+ ```swift
286
+ import SwiftUI
287
+ import NipworkerSwift
288
+
289
+ @MainActor
290
+ class FeedViewModel: ObservableObject {
291
+ let manager = NostrManager()
292
+ let sub: Subscription
293
+
294
+ init() {
295
+ sub = Subscription(id: "feed", manager: manager)
296
+ }
297
+
298
+ func start() async {
299
+ let request = RequestObject(
300
+ filters: [.init(kinds: [1], limit: 50)],
301
+ relays: ["wss://relay.damus.io"]
302
+ )
303
+ _ = await manager.subscribe(
304
+ subscriptionId: "feed",
305
+ requests: [request]
306
+ )
307
+ }
308
+ }
309
+
310
+ struct FeedView: View {
311
+ @StateObject var vm = FeedViewModel()
312
+
313
+ var body: some View {
314
+ List(vm.sub.events) { event in
315
+ VStack(alignment: .leading) {
316
+ Text(event.content)
317
+ .lineLimit(3)
318
+ Text("Kind \(event.kind)")
319
+ .font(.caption)
320
+ .foregroundColor(.secondary)
321
+ }
322
+ }
323
+ .task {
324
+ await vm.start()
325
+ }
326
+ }
327
+ }
328
+ ```
329
+
330
+ ### Native login and signer restore
331
+
332
+ `NostrManager` keeps the active signer in memory only. It does not persist private
333
+ keys, NIP-46 session data, or login state. Native apps should own that policy with
334
+ their own secure storage, typically Keychain on iOS/macOS.
335
+
336
+ On first login, validate the key by installing it as the signer and asking the
337
+ native engine for the public key:
338
+
339
+ ```swift
340
+ let secret = "your-hex-private-key"
341
+ manager.setSigner(.privkey(secret))
342
+ let pubkey = try await manager.getPublicKey()
343
+
344
+ let session = AuthSession(
345
+ pubkey: pubkey,
346
+ signer: .privateKey(secret)
347
+ )
348
+
349
+ // App-owned persistence. Store this in Keychain, not UserDefaults.
350
+ let data = try JSONEncoder().encode(session)
351
+ try await keychain.save(data, account: "nostr-auth")
352
+ ```
353
+
354
+ On app restart, load the saved session and restore the native signer explicitly:
355
+
356
+ ```swift
357
+ if let data = try await keychain.load(account: "nostr-auth") {
358
+ let session = try JSONDecoder().decode(AuthSession.self, from: data)
359
+
360
+ switch session.signer {
361
+ case .privateKey(let secret):
362
+ manager.setSigner(.privkey(secret))
363
+ _ = try await manager.getPublicKey()
364
+ }
365
+ }
366
+ ```
367
+
368
+ The library exposes `AuthSession` as a small `Codable` value so app code can use
369
+ one stable storage format. The actual storage backend is intentionally outside
370
+ `NostrManager`; the host app decides whether to persist, when to restore, whether
371
+ to use biometric gates, and how logout deletes credentials.
372
+
373
+ `logout()` clears Swift-side auth state for the manager. If the app persisted an
374
+ `AuthSession`, it must also delete that saved session from its own secure store.
375
+
376
+ ### Publish a signed event
377
+
378
+ ```swift
379
+ manager.setSigner(.privkey("your-hex-private-key"))
380
+ _ = try await manager.getPublicKey()
381
+
382
+ let template = EventTemplate(
383
+ kind: 1,
384
+ content: "Hello from Swift!",
385
+ tags: []
386
+ )
387
+ let event = try await manager.signEvent(template)
388
+
389
+ let tracker = PublishTracker(publishId: "pub-1", manager: manager)
390
+
391
+ _ = await manager.publish(
392
+ publishId: "pub-1",
393
+ event: event,
394
+ defaultRelays: ["wss://relay.damus.io"]
395
+ )
396
+
397
+ // tracker.statuses will update automatically via @Published
398
+ ```
399
+
400
+ ### Monitor relay status
401
+
402
+ ```swift
403
+ let unsub = useRelayStatus(manager: manager) { relay, status in
404
+ print("Relay \(relay): \(status)")
405
+ }
406
+ ```
407
+
408
+ ## Architecture
409
+
410
+ - **Rust FFI** — A static library (`libnipworker_native_ffi.a`) compiled from the `native-ffi` crate and bundled as an `XCFramework`.
411
+ - **FlatBuffers** — All messages crossing the Swift/Rust boundary are serialized with FlatBuffers for zero-allocation parsing on the Rust side.
412
+ - **Actor Isolation** — `NostrManager` is a Swift `actor` so all state mutation is serialized and safe to call from any concurrency context.
413
+ - **Subscription Buffers** — Events arrive from Rust into a shared `SubscriptionBuffer` (a raw pointer with a 4-byte header). Swift reads length-prefixed messages out of the buffer.
414
+
415
+ ## Testing
416
+
417
+ Run the test suite from the `swift/` directory:
418
+
419
+ ```bash
420
+ cd swift
421
+ swift test
422
+ ```
423
+
424
+ The suite includes:
425
+ - **Unit tests** — `ArrayBufferReaderTests`, `NostrManagerTests`
426
+ - **Integration tests** — Live WebSocket subscription against `wss://relay.damus.io`
427
+
428
+ > Integration tests require an internet connection and may take a few seconds.
429
+
430
+ ## Known Limitations
431
+
432
+ - **Swift 6 language mode warnings** — The package compiles and runs correctly, but produces a handful of concurrency warnings (e.g., accessing `handle`/`boxPtr` in the actor initializer). These are non-blocking.
433
+ - **FlatBuffers Object API** — Unavailable due to a `flatc` code-generation bug (`ParsedDataUnion` enum/struct name collision). All serialization uses the low-level builder API.
434
+ - **Native deployment target warnings** — Rebuild `NipworkerNativeFFI.xcframework` with `crates/native-ffi/ios/build-ios.sh`. The script passes explicit iOS and macOS minimum-version linker flags to Rust so the compiled static libraries match the Swift package deployment targets.
435
+
436
+ ## License
437
+
438
+ Same as the parent NIPWorker project.
@@ -0,0 +1,136 @@
1
+ import Foundation
2
+
3
+ /// Wraps a raw mutable buffer allocated with UnsafeMutablePointer.
4
+ /// Buffer format:
5
+ /// [0-3]: write position (UInt32 little-endian)
6
+ /// [4..]: [4-byte len][FlatBuffer payload]...
7
+ public final class SubscriptionBuffer {
8
+ public let pointer: UnsafeMutableRawPointer
9
+ public let capacity: Int
10
+ private let ownsMemory: Bool
11
+
12
+ public init(capacity: Int) {
13
+ self.pointer = .allocate(byteCount: capacity, alignment: MemoryLayout<UInt32>.alignment)
14
+ self.capacity = capacity
15
+ self.ownsMemory = true
16
+ // Initialize write position to 4 (right after header)
17
+ pointer.storeBytes(of: UInt32(4).littleEndian, toByteOffset: 0, as: UInt32.self)
18
+ }
19
+
20
+ public init(pointer: UnsafeMutableRawPointer, capacity: Int) {
21
+ self.pointer = pointer
22
+ self.capacity = capacity
23
+ self.ownsMemory = false
24
+ }
25
+
26
+ deinit {
27
+ if ownsMemory {
28
+ pointer.deallocate()
29
+ }
30
+ }
31
+ }
32
+
33
+ public enum ArrayBufferReader {
34
+ public static func initializeBuffer(_ buffer: SubscriptionBuffer) {
35
+ buffer.pointer.storeBytes(of: UInt32(4).littleEndian, toByteOffset: 0, as: UInt32.self)
36
+ }
37
+
38
+ public static func writeMessage(buffer: SubscriptionBuffer, data: Data) -> Bool {
39
+ let currentWritePosition = readWritePosition(buffer)
40
+ let requiredSpace = 4 + data.count
41
+ guard currentWritePosition + requiredSpace <= buffer.capacity else {
42
+ return false
43
+ }
44
+
45
+ buffer.pointer.storeBytes(
46
+ of: UInt32(data.count).littleEndian,
47
+ toByteOffset: currentWritePosition,
48
+ as: UInt32.self
49
+ )
50
+ data.copyBytes(to: buffer.pointer.advanced(by: currentWritePosition + 4).assumingMemoryBound(to: UInt8.self), count: data.count)
51
+
52
+ let newPosition = currentWritePosition + requiredSpace
53
+ buffer.pointer.storeBytes(of: UInt32(newPosition).littleEndian, toByteOffset: 0, as: UInt32.self)
54
+ return true
55
+ }
56
+
57
+ public static func writeBatchedData(buffer: SubscriptionBuffer, data: Data, debugId: String? = nil) -> Bool {
58
+ let currentWritePosition = readWritePosition(buffer)
59
+ guard currentWritePosition + data.count <= buffer.capacity else {
60
+ return false
61
+ }
62
+
63
+ data.copyBytes(to: buffer.pointer.advanced(by: currentWritePosition).assumingMemoryBound(to: UInt8.self), count: data.count)
64
+
65
+ let newPosition = currentWritePosition + data.count
66
+ buffer.pointer.storeBytes(of: UInt32(newPosition).littleEndian, toByteOffset: 0, as: UInt32.self)
67
+ return true
68
+ }
69
+
70
+ public static func readMessages(
71
+ buffer: SubscriptionBuffer,
72
+ lastReadPosition: Int = 0
73
+ ) -> (messages: [Data], newReadPosition: Int, hasNewData: Bool) {
74
+ let currentWritePosition = readWritePosition(buffer)
75
+ let dataStartOffset = 4
76
+ var currentPos = max(lastReadPosition, dataStartOffset)
77
+
78
+ guard currentWritePosition > currentPos else {
79
+ return ([], currentPos, false)
80
+ }
81
+
82
+ var messages: [Data] = []
83
+ let maxMessages = 128
84
+
85
+ while currentPos < currentWritePosition && messages.count < maxMessages {
86
+ guard currentPos + 4 <= currentWritePosition else { break }
87
+
88
+ let eventLength = Int(
89
+ UInt32(buffer.pointer.load(fromByteOffset: currentPos, as: UInt8.self)) |
90
+ (UInt32(buffer.pointer.load(fromByteOffset: currentPos + 1, as: UInt8.self)) << 8) |
91
+ (UInt32(buffer.pointer.load(fromByteOffset: currentPos + 2, as: UInt8.self)) << 16) |
92
+ (UInt32(buffer.pointer.load(fromByteOffset: currentPos + 3, as: UInt8.self)) << 24)
93
+ )
94
+ let payloadStart = currentPos + 4
95
+ let nextPos = payloadStart + eventLength
96
+
97
+ guard nextPos <= currentWritePosition else { break }
98
+
99
+ let slice = Data(bytes: buffer.pointer.advanced(by: payloadStart), count: eventLength)
100
+ messages.append(slice)
101
+ currentPos = nextPos
102
+ }
103
+
104
+ return (messages, currentPos, messages.count > 0)
105
+ }
106
+
107
+ public static func readAllMessages(buffer: SubscriptionBuffer) -> (messages: [Data], totalMessages: Int) {
108
+ let result = readMessages(buffer: buffer, lastReadPosition: 0)
109
+ return (result.messages, result.messages.count)
110
+ }
111
+
112
+ public static func getCurrentWritePosition(buffer: SubscriptionBuffer) -> Int {
113
+ return readWritePosition(buffer)
114
+ }
115
+
116
+ public static func hasNewData(buffer: SubscriptionBuffer, lastReadPosition: Int) -> Bool {
117
+ let currentWritePosition = readWritePosition(buffer)
118
+ return currentWritePosition > max(lastReadPosition, 4)
119
+ }
120
+
121
+ public static func calculateBufferSize(totalEventLimit: Int = 100, bytesPerEvent: Int = 3072) -> Int {
122
+ let headerSize = 4
123
+ let dataSize = totalEventLimit * bytesPerEvent
124
+ let overhead = Int(Double(dataSize) * 0.25)
125
+ return headerSize + dataSize + overhead
126
+ }
127
+
128
+ private static func readWritePosition(_ buffer: SubscriptionBuffer) -> Int {
129
+ return Int(
130
+ UInt32(buffer.pointer.load(fromByteOffset: 0, as: UInt8.self)) |
131
+ (UInt32(buffer.pointer.load(fromByteOffset: 1, as: UInt8.self)) << 8) |
132
+ (UInt32(buffer.pointer.load(fromByteOffset: 2, as: UInt8.self)) << 16) |
133
+ (UInt32(buffer.pointer.load(fromByteOffset: 3, as: UInt8.self)) << 24)
134
+ )
135
+ }
136
+ }
@@ -0,0 +1,24 @@
1
+ import Foundation
2
+
3
+ public enum AuthSigner: Codable, Equatable, Sendable {
4
+ case privateKey(String)
5
+ }
6
+
7
+ public struct AuthSession: Codable, Equatable, Sendable {
8
+ public var pubkey: String
9
+ public var signer: AuthSigner
10
+ public var createdAt: Date
11
+ public var lastUsedAt: Date?
12
+
13
+ public init(
14
+ pubkey: String,
15
+ signer: AuthSigner,
16
+ createdAt: Date = Date(),
17
+ lastUsedAt: Date? = nil
18
+ ) {
19
+ self.pubkey = pubkey
20
+ self.signer = signer
21
+ self.createdAt = createdAt
22
+ self.lastUsedAt = lastUsedAt
23
+ }
24
+ }
@@ -0,0 +1,94 @@
1
+ import Darwin
2
+ import Foundation
3
+
4
+ // MARK: - C FFI Imports from libnipworker_native_ffi
5
+
6
+ @_silgen_name("nipworker_init")
7
+ func nipworker_init(
8
+ _ callback: @convention(c) (UnsafeMutableRawPointer?, UnsafePointer<UInt8>?, Int) -> Void,
9
+ _ userdata: UnsafeMutableRawPointer?
10
+ ) -> UnsafeMutableRawPointer?
11
+
12
+ @_silgen_name("nipworker_handle_message")
13
+ func nipworker_handle_message(
14
+ _ handle: UnsafeMutableRawPointer?,
15
+ _ ptr: UnsafePointer<UInt8>?,
16
+ _ len: Int
17
+ )
18
+
19
+ @_silgen_name("nipworker_subscribe_message")
20
+ func nipworker_subscribe_message(
21
+ _ handle: UnsafeMutableRawPointer?,
22
+ _ ptr: UnsafePointer<UInt8>?,
23
+ _ len: Int
24
+ ) -> Bool
25
+
26
+ @_silgen_name("nipworker_publish_message")
27
+ func nipworker_publish_message(
28
+ _ handle: UnsafeMutableRawPointer?,
29
+ _ ptr: UnsafePointer<UInt8>?,
30
+ _ len: Int
31
+ ) -> Bool
32
+
33
+ @_silgen_name("nipworker_set_private_key")
34
+ func nipworker_set_private_key(
35
+ _ handle: UnsafeMutableRawPointer?,
36
+ _ ptr: UnsafePointer<Int8>?
37
+ )
38
+
39
+ @_silgen_name("nipworker_deinit")
40
+ func nipworker_deinit(_ handle: UnsafeMutableRawPointer?)
41
+
42
+ @_silgen_name("nipworker_free_bytes")
43
+ func nipworker_free_bytes(_ ptr: UnsafeMutablePointer<UInt8>?, _ len: Int)
44
+
45
+ @_silgen_name("nipworker_register_subscription")
46
+ func nipworker_register_subscription(
47
+ _ handle: UnsafeMutableRawPointer?,
48
+ _ subId: UnsafePointer<Int8>?,
49
+ _ bufferSize: Int
50
+ ) -> Bool
51
+
52
+ @_silgen_name("nipworker_register_publish_buffer")
53
+ func nipworker_register_publish_buffer(
54
+ _ handle: UnsafeMutableRawPointer?,
55
+ _ publishId: UnsafePointer<Int8>?,
56
+ _ bufferSize: Int
57
+ ) -> Bool
58
+
59
+ @_silgen_name("nipworker_retain_subscription")
60
+ func nipworker_retain_subscription(
61
+ _ handle: UnsafeMutableRawPointer?,
62
+ _ subId: UnsafePointer<Int8>?
63
+ ) -> Bool
64
+
65
+ @_silgen_name("nipworker_release_subscription")
66
+ func nipworker_release_subscription(
67
+ _ handle: UnsafeMutableRawPointer?,
68
+ _ subId: UnsafePointer<Int8>?
69
+ )
70
+
71
+ @_silgen_name("nipworker_subscription_buffer_ptr")
72
+ func nipworker_subscription_buffer_ptr(
73
+ _ handle: UnsafeMutableRawPointer?,
74
+ _ subId: UnsafePointer<Int8>?
75
+ ) -> UnsafeMutablePointer<UInt8>?
76
+
77
+ @_silgen_name("nipworker_subscription_buffer_len")
78
+ func nipworker_subscription_buffer_len(
79
+ _ handle: UnsafeMutableRawPointer?,
80
+ _ subId: UnsafePointer<Int8>?
81
+ ) -> Int
82
+
83
+ @_silgen_name("nipworker_cleanup_subscriptions")
84
+ func nipworker_cleanup_subscriptions(_ handle: UnsafeMutableRawPointer?)
85
+
86
+ func nipworker_react_native_shared_handle_if_available() -> UnsafeMutableRawPointer? {
87
+ typealias SharedHandleFunction = @convention(c) () -> UnsafeMutableRawPointer?
88
+
89
+ guard let symbol = dlsym(UnsafeMutableRawPointer(bitPattern: -2), "nipworker_react_native_shared_handle") else {
90
+ return nil
91
+ }
92
+
93
+ return unsafeBitCast(symbol, to: SharedHandleFunction.self)()
94
+ }