@sudobility/building_blocks_rn 0.0.9 → 0.0.11

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 (46) hide show
  1. package/building-blocks-rn-macos.podspec +20 -0
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.js +4 -0
  4. package/dist/src/api/ApiContext.d.ts +10 -0
  5. package/dist/src/api/ApiContext.js +10 -0
  6. package/dist/src/app/SudobilityAppRN.d.ts +13 -7
  7. package/dist/src/app/SudobilityAppRN.js +11 -8
  8. package/dist/src/components/error/ErrorBoundary.d.ts +40 -0
  9. package/dist/src/components/error/ErrorBoundary.js +85 -0
  10. package/dist/src/components/error/index.d.ts +2 -0
  11. package/dist/src/components/error/index.js +1 -0
  12. package/dist/src/components/footer/AppFooter.js +10 -1
  13. package/dist/src/components/header/AppHeader.d.ts +8 -0
  14. package/dist/src/components/header/AppHeader.js +3 -3
  15. package/dist/src/components/layout/AppScreenLayout.d.ts +7 -0
  16. package/dist/src/components/pages/AppSubscriptionPage.js +19 -5
  17. package/dist/src/components/pages/AppTextScreen.d.ts +8 -0
  18. package/dist/src/components/pages/AppTextScreen.js +2 -2
  19. package/dist/src/components/pages/LoginScreen.js +18 -3
  20. package/dist/src/components/settings/AppearanceSettings.js +18 -14
  21. package/dist/src/components/settings/LanguagePicker.js +20 -5
  22. package/dist/src/components/settings/SettingsListScreen.js +4 -4
  23. package/dist/src/components/subscription/SafeSubscriptionContext.js +9 -0
  24. package/dist/src/components/subscription/SubscriptionScreen.js +17 -4
  25. package/dist/src/components/toast/ToastProvider.d.ts +23 -0
  26. package/dist/src/components/toast/ToastProvider.js +46 -6
  27. package/dist/src/constants/languages.d.ts +8 -0
  28. package/dist/src/constants/languages.js +8 -0
  29. package/dist/src/i18n/index.d.ts +6 -3
  30. package/dist/src/i18n/index.js +6 -3
  31. package/dist/src/native/WebAuth.d.ts +3 -0
  32. package/dist/src/native/WebAuth.js +20 -0
  33. package/dist/src/theme/ThemeContext.d.ts +14 -0
  34. package/dist/src/theme/ThemeContext.js +35 -7
  35. package/dist/src/theme/colors.d.ts +6 -2
  36. package/dist/src/theme/colors.js +6 -2
  37. package/dist/src/theme/spacing.d.ts +5 -2
  38. package/dist/src/theme/spacing.js +5 -2
  39. package/dist/src/theme/typography.d.ts +6 -2
  40. package/dist/src/theme/typography.js +6 -2
  41. package/macos/WebAuthModule.h +4 -0
  42. package/macos/WebAuthModule.m +104 -0
  43. package/package.json +19 -11
  44. package/react-native.config.js +11 -0
  45. package/windows/WebAuthModule.cpp +245 -0
  46. package/windows/WebAuthModule.h +29 -0
@@ -0,0 +1,245 @@
1
+ #include "pch.h"
2
+ #include "WebAuthModule.h"
3
+
4
+ #include <bcrypt.h>
5
+ #include <wincrypt.h>
6
+ #include <shellapi.h>
7
+ #include <winsock2.h>
8
+ #include <ws2tcpip.h>
9
+
10
+ #include <string>
11
+ #include <vector>
12
+ #include <thread>
13
+ #include <regex>
14
+
15
+ #pragma comment(lib, "bcrypt.lib")
16
+ #pragma comment(lib, "crypt32.lib")
17
+ #pragma comment(lib, "ws2_32.lib")
18
+
19
+ namespace BuildingBlocksRN {
20
+
21
+ void WebAuthModule::Initialize(
22
+ winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept {
23
+ m_reactContext = reactContext;
24
+ }
25
+
26
+ std::string WebAuthModule::Base64UrlEncode(const std::vector<uint8_t> &data) {
27
+ DWORD base64Len = 0;
28
+ CryptBinaryToStringA(data.data(), static_cast<DWORD>(data.size()),
29
+ CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr,
30
+ &base64Len);
31
+
32
+ std::string base64(base64Len, '\0');
33
+ CryptBinaryToStringA(data.data(), static_cast<DWORD>(data.size()),
34
+ CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF,
35
+ base64.data(), &base64Len);
36
+ base64.resize(base64Len);
37
+
38
+ // Convert base64 to base64url
39
+ std::string result;
40
+ result.reserve(base64.size());
41
+ for (char c : base64) {
42
+ if (c == '+')
43
+ result += '-';
44
+ else if (c == '/')
45
+ result += '_';
46
+ else if (c == '=')
47
+ continue; // strip padding
48
+ else
49
+ result += c;
50
+ }
51
+ return result;
52
+ }
53
+
54
+ void WebAuthModule::generateCodeVerifier(
55
+ React::ReactPromise<std::string> result) noexcept {
56
+ std::vector<uint8_t> randomBytes(32);
57
+ NTSTATUS status =
58
+ BCryptGenRandom(nullptr, randomBytes.data(),
59
+ static_cast<ULONG>(randomBytes.size()),
60
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG);
61
+ if (!BCRYPT_SUCCESS(status)) {
62
+ result.Reject(React::ReactError{
63
+ "RANDOM_ERROR", "Failed to generate random bytes"});
64
+ return;
65
+ }
66
+
67
+ result.Resolve(Base64UrlEncode(randomBytes));
68
+ }
69
+
70
+ void WebAuthModule::sha256(std::string input,
71
+ React::ReactPromise<std::string> result) noexcept {
72
+ BCRYPT_ALG_HANDLE hAlg = nullptr;
73
+ BCRYPT_HASH_HANDLE hHash = nullptr;
74
+ NTSTATUS status;
75
+
76
+ status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, nullptr,
77
+ 0);
78
+ if (!BCRYPT_SUCCESS(status)) {
79
+ result.Reject(
80
+ React::ReactError{"HASH_ERROR", "Failed to open algorithm provider"});
81
+ return;
82
+ }
83
+
84
+ status = BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0);
85
+ if (!BCRYPT_SUCCESS(status)) {
86
+ BCryptCloseAlgorithmProvider(hAlg, 0);
87
+ result.Reject(
88
+ React::ReactError{"HASH_ERROR", "Failed to create hash"});
89
+ return;
90
+ }
91
+
92
+ status = BCryptHashData(hHash,
93
+ reinterpret_cast<PUCHAR>(
94
+ const_cast<char *>(input.data())),
95
+ static_cast<ULONG>(input.size()), 0);
96
+ if (!BCRYPT_SUCCESS(status)) {
97
+ BCryptDestroyHash(hHash);
98
+ BCryptCloseAlgorithmProvider(hAlg, 0);
99
+ result.Reject(
100
+ React::ReactError{"HASH_ERROR", "Failed to hash data"});
101
+ return;
102
+ }
103
+
104
+ std::vector<uint8_t> hashValue(32); // SHA-256 = 32 bytes
105
+ status = BCryptFinishHash(hHash, hashValue.data(),
106
+ static_cast<ULONG>(hashValue.size()), 0);
107
+ BCryptDestroyHash(hHash);
108
+ BCryptCloseAlgorithmProvider(hAlg, 0);
109
+
110
+ if (!BCRYPT_SUCCESS(status)) {
111
+ result.Reject(
112
+ React::ReactError{"HASH_ERROR", "Failed to finish hash"});
113
+ return;
114
+ }
115
+
116
+ result.Resolve(Base64UrlEncode(hashValue));
117
+ }
118
+
119
+ void WebAuthModule::authenticate(
120
+ std::string url, std::string callbackScheme,
121
+ React::ReactPromise<React::JSValue> result) noexcept {
122
+ // Launch authentication on a background thread to avoid blocking UI
123
+ std::thread([url = std::move(url), callbackScheme = std::move(callbackScheme),
124
+ result = std::move(result)]() mutable {
125
+ // Initialize Winsock for this thread
126
+ WSADATA wsaData;
127
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
128
+ result.Reject(
129
+ React::ReactError{"SOCKET_ERROR", "Failed to initialize Winsock"});
130
+ return;
131
+ }
132
+
133
+ // Create a TCP listener on a random port
134
+ SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
135
+ if (listenSock == INVALID_SOCKET) {
136
+ WSACleanup();
137
+ result.Reject(
138
+ React::ReactError{"SOCKET_ERROR", "Failed to create socket"});
139
+ return;
140
+ }
141
+
142
+ sockaddr_in addr{};
143
+ addr.sin_family = AF_INET;
144
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
145
+ addr.sin_port = 0; // OS picks a free port
146
+
147
+ if (bind(listenSock, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) ==
148
+ SOCKET_ERROR) {
149
+ closesocket(listenSock);
150
+ WSACleanup();
151
+ result.Reject(
152
+ React::ReactError{"SOCKET_ERROR", "Failed to bind socket"});
153
+ return;
154
+ }
155
+
156
+ // Get the assigned port
157
+ int addrLen = sizeof(addr);
158
+ getsockname(listenSock, reinterpret_cast<sockaddr *>(&addr), &addrLen);
159
+ int port = ntohs(addr.sin_port);
160
+
161
+ if (listen(listenSock, 1) == SOCKET_ERROR) {
162
+ closesocket(listenSock);
163
+ WSACleanup();
164
+ result.Reject(
165
+ React::ReactError{"SOCKET_ERROR", "Failed to listen on socket"});
166
+ return;
167
+ }
168
+
169
+ // Build the redirect URI and full auth URL
170
+ std::string redirectUri =
171
+ "http://127.0.0.1:" + std::to_string(port) + "/callback";
172
+
173
+ // Append redirect_uri to the auth URL
174
+ std::string fullUrl = url;
175
+ if (fullUrl.find('?') != std::string::npos)
176
+ fullUrl += "&redirect_uri=" + redirectUri;
177
+ else
178
+ fullUrl += "?redirect_uri=" + redirectUri;
179
+
180
+ // Open browser
181
+ std::wstring wUrl(fullUrl.begin(), fullUrl.end());
182
+ ShellExecuteW(nullptr, L"open", wUrl.c_str(), nullptr, nullptr,
183
+ SW_SHOWNORMAL);
184
+
185
+ // Set a timeout on the listen socket (60 seconds)
186
+ DWORD timeout = 60000;
187
+ setsockopt(listenSock, SOL_SOCKET, SO_RCVTIMEO,
188
+ reinterpret_cast<const char *>(&timeout), sizeof(timeout));
189
+
190
+ // Accept the redirect connection
191
+ SOCKET clientSock = accept(listenSock, nullptr, nullptr);
192
+ if (clientSock == INVALID_SOCKET) {
193
+ closesocket(listenSock);
194
+ WSACleanup();
195
+ result.Resolve(React::JSValue{nullptr}); // timeout / cancelled
196
+ return;
197
+ }
198
+
199
+ // Read the HTTP request
200
+ char buf[4096];
201
+ int bytesRead = recv(clientSock, buf, sizeof(buf) - 1, 0);
202
+ if (bytesRead <= 0) {
203
+ closesocket(clientSock);
204
+ closesocket(listenSock);
205
+ WSACleanup();
206
+ result.Resolve(React::JSValue{nullptr});
207
+ return;
208
+ }
209
+ buf[bytesRead] = '\0';
210
+
211
+ // Send a simple HTML response to close the browser tab
212
+ const char *response =
213
+ "HTTP/1.1 200 OK\r\n"
214
+ "Content-Type: text/html\r\n"
215
+ "Connection: close\r\n\r\n"
216
+ "<html><body><p>Authentication complete. You may close this "
217
+ "tab.</p><script>window.close()</script></body></html>";
218
+ send(clientSock, response, static_cast<int>(strlen(response)), 0);
219
+ closesocket(clientSock);
220
+ closesocket(listenSock);
221
+ WSACleanup();
222
+
223
+ // Parse the request line to extract the callback URL
224
+ std::string request(buf);
225
+ // Extract GET /callback?... HTTP/1.1
226
+ std::regex requestLineRegex(R"(GET\s+(/\S+)\s+HTTP)");
227
+ std::smatch match;
228
+ if (std::regex_search(request, match, requestLineRegex)) {
229
+ std::string path = match[1].str();
230
+ auto qPos = path.find('?');
231
+ if (qPos == std::string::npos) {
232
+ result.Resolve(React::JSValue{nullptr});
233
+ return;
234
+ }
235
+ // Reconstruct the full callback URL
236
+ std::string callbackUrl =
237
+ callbackScheme + "://callback" + path.substr(qPos);
238
+ result.Resolve(React::JSValue{callbackUrl});
239
+ } else {
240
+ result.Resolve(React::JSValue{nullptr});
241
+ }
242
+ }).detach();
243
+ }
244
+
245
+ } // namespace BuildingBlocksRN
@@ -0,0 +1,29 @@
1
+ #pragma once
2
+
3
+ #include "pch.h"
4
+ #include "NativeModules.h"
5
+ #include <winrt/Microsoft.ReactNative.h>
6
+
7
+ namespace BuildingBlocksRN {
8
+
9
+ REACT_MODULE(WebAuthModule)
10
+ struct WebAuthModule {
11
+ REACT_INIT(Initialize)
12
+ void Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept;
13
+
14
+ REACT_METHOD(generateCodeVerifier)
15
+ void generateCodeVerifier(React::ReactPromise<std::string> result) noexcept;
16
+
17
+ REACT_METHOD(sha256)
18
+ void sha256(std::string input, React::ReactPromise<std::string> result) noexcept;
19
+
20
+ REACT_METHOD(authenticate)
21
+ void authenticate(std::string url, std::string callbackScheme,
22
+ React::ReactPromise<React::JSValue> result) noexcept;
23
+
24
+ private:
25
+ static std::string Base64UrlEncode(const std::vector<uint8_t> &data);
26
+ winrt::Microsoft::ReactNative::ReactContext m_reactContext;
27
+ };
28
+
29
+ } // namespace BuildingBlocksRN