-
Notifications
You must be signed in to change notification settings - Fork 1
feat(llc): implement real guest token flow via createGuest #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
d7de531
feat(llc): implement real guest token flow via createGuest
renefloor d5df62f
chore: trigger CI re-run
renefloor d3e686a
test(llc): add coverage for guest user token flow
renefloor 477415c
test(llc): fix lint warnings in guest user test
renefloor c199264
Fix guest token userId
renefloor a84144d
Make tokenManager mutable
renefloor 6d2b98c
update core dependency
renefloor 5359141
add ignore for git dependency
renefloor 97dbca9
improve on private docs
renefloor 933e9b8
chore(deps): pin stream_core to the branch carrying the connection re…
xsahil03x faef1d9
feat(llc): adopt the reworked connection and logging APIs
xsahil03x a5582c7
test(llc): cover connect, disconnect and the failures around them
xsahil03x ae0df1e
docs(llc): describe the connection lifecycle and logging
xsahil03x abe4ae3
feat(sample): sign in as a guest, and report through the logger
xsahil03x d1a781d
chore(sample): drop the unused splash shell
xsahil03x a33cdcd
chore(deps): move the stream_core pin to the current branch tip
xsahil03x 3d861f0
chore: raise the minimum Dart SDK to ^3.12.0
xsahil03x 4f0f61d
docs(changelog): match the style the released sections use
xsahil03x 5d7fffe
docs(changelog): use the section style stream_core uses
xsahil03x f9e62d4
docs(changelog): drop what a reader cannot act on
xsahil03x 3313f42
docs(changelog): stop listing what the compiler already names
xsahil03x 985a209
docs(changelog): put the rename table back, and make it a table
xsahil03x 393c2d4
Merge branch 'main' into renefloor/flu-373-guest-and-anonymous-login
xsahil03x 21049a6
docs: release the client each authentication snippet builds
xsahil03x 0189de5
Merge branch 'main' into renefloor/flu-373-guest-and-anonymous-login
xsahil03x 70cd0a3
ci(repo): resolve dependencies one package at a time
xsahil03x e4144a1
chore(deps): pin stream_core to main
xsahil03x d9a0786
feat(llc): report request headers, and stop claiming the token stays …
xsahil03x 8317b93
style(llc): pass the interceptors their positional arguments first
xsahil03x 23aba14
Merge branch 'main' into renefloor/flu-373-guest-and-anonymous-login
xsahil03x c984fc2
docs: document the logging API the client actually has
xsahil03x 338f52a
fix docs mistakes and minor improvements
renefloor 65cde25
refactor(llc): share the guest exchange through core's in-flight cache
xsahil03x 156ccbe
Merge remote-tracking branch 'origin/main' into HEAD
xsahil03x c50940f
fix(repo): sync melos' shared `stream_core` pin to f83b5d4
xsahil03x File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import 'package:stream_feeds/stream_feeds.dart'; | ||
|
|
||
| Future<void> regularUserLogin() async { | ||
| // Regular user: provide a JWT token (from your server). | ||
| final client = StreamFeedsClient( | ||
| apiKey: '<your_api_key>', | ||
| user: const User(id: 'alice'), | ||
| tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')), | ||
| ); | ||
| await client.connect(); | ||
|
|
||
| // Terminal, and what a real app calls when it is done with the client for good — | ||
| // on sign-out, say. Use `disconnect` to close the connection and keep the client. | ||
| await client.dispose(); | ||
| } | ||
|
|
||
| Future<void> dynamicTokenProvider() async { | ||
| // Dynamic token provider: fetches a new token from your server | ||
| // when the current one expires. | ||
| final client = StreamFeedsClient( | ||
| apiKey: '<your_api_key>', | ||
| user: const User(id: 'alice'), | ||
| tokenProvider: TokenProvider.dynamic((userId) async { | ||
| // Fetch a fresh JWT for `userId` from your backend. | ||
| final token = await fetchTokenFromYourServer(userId); | ||
| return UserToken(token); | ||
| }), | ||
| ); | ||
| await client.connect(); | ||
| await client.dispose(); | ||
| } | ||
|
|
||
| // Placeholder for your server token fetch | ||
| Future<String> fetchTokenFromYourServer(String userId) async => '<jwt>'; | ||
|
|
||
| Future<void> guestUserLogin() async { | ||
| // Guest user: the SDK obtains a temporary token during connect, so no | ||
| // tokenProvider is needed. The session is temporary and is not tied to a | ||
| // persistent account. | ||
| final client = StreamFeedsClient( | ||
| apiKey: '<your_api_key>', | ||
| user: User.guest('guest-${DateTime.now().millisecondsSinceEpoch}'), | ||
| ); | ||
| await client.connect(); | ||
|
|
||
| // The server assigns the guest its own id, so read it from `client.user` | ||
| // rather than reusing the id you asked for. | ||
| final feed = client.feed(group: 'user', id: client.user.id); | ||
| await feed.getOrCreate(); | ||
|
|
||
| await client.dispose(); | ||
| } | ||
|
|
||
| Future<void> anonymousUserLogin() async { | ||
| // Anonymous user: no token of its own and no WebSocket connection. Use it to | ||
| // read public feeds. Calling connect() is not required, and opens no | ||
| // connection for an anonymous user. | ||
| final client = StreamFeedsClient( | ||
| apiKey: '<your_api_key>', | ||
| user: const User.anonymous(), | ||
| ); | ||
|
|
||
| // Watching requires a connection, so ask for a feed that is not watched. | ||
| final feed = client.feedFromQuery( | ||
| const FeedQuery( | ||
| fid: FeedId(group: 'user', id: 'alice'), | ||
| watch: false, | ||
| ), | ||
| ); | ||
| await feed.getOrCreate(); | ||
|
|
||
| await client.dispose(); | ||
| } | ||
|
|
||
| Future<void> requestOnlyLogin() async { | ||
| // Authenticate without opening a WebSocket, for a client that only makes | ||
| // requests. No events are emitted, and a watched query is rejected because | ||
| // watching requires a connection. | ||
| final client = StreamFeedsClient( | ||
| apiKey: '<your_api_key>', | ||
| user: const User(id: 'alice'), | ||
| tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')), | ||
| ); | ||
| await client.connect(connectWebSocket: false); | ||
|
|
||
| final feed = client.feedFromQuery( | ||
| const FeedQuery( | ||
| fid: FeedId(group: 'user', id: 'alice'), | ||
| watch: false, | ||
| ), | ||
| ); | ||
| await feed.getOrCreate(); | ||
|
|
||
| await client.dispose(); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import 'package:flutter/foundation.dart'; | ||
| import 'package:stream_feeds/stream_feeds.dart'; | ||
|
|
||
| Future<void> seeWhatTheClientIsDoing() async { | ||
| // Nothing is logged until you ask. A priority on its own writes to the console. | ||
| final client = StreamFeedsClient( | ||
| apiKey: '<your_api_key>', | ||
| user: const User(id: 'alice'), | ||
| tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')), | ||
| config: const FeedsConfig( | ||
| logConfig: StreamLogConfig(priority: StreamLogPriority.debug), | ||
| ), | ||
| ); | ||
| await client.connect(); | ||
|
|
||
| // Terminal, and what a real app calls when it is done with the client for good. Use `disconnect` | ||
| // to close the connection and keep the client. | ||
| await client.dispose(); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| Future<void> sendRecordsSomewhereElse() async { | ||
| // A handler of your own replaces the console. | ||
| final client = StreamFeedsClient( | ||
| apiKey: '<your_api_key>', | ||
| user: const User(id: 'alice'), | ||
| tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')), | ||
| config: const FeedsConfig( | ||
| logConfig: StreamLogConfig( | ||
| priority: StreamLogPriority.debug, | ||
| handler: StreamLogHandler.from(reportToYourCrashReporter), | ||
| ), | ||
| ), | ||
| ); | ||
| await client.connect(); | ||
| await client.dispose(); | ||
| } | ||
|
|
||
| Future<void> keepTheConsoleAsWell() async { | ||
| // Or compose with the one the client would have used. Naming it only under `kDebugMode` keeps a | ||
| // console for whoever is developing without leaving one in the build your users run, while the | ||
| // crash reporter goes on receiving records everywhere. | ||
| final client = StreamFeedsClient( | ||
| apiKey: '<your_api_key>', | ||
| user: const User(id: 'alice'), | ||
| tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')), | ||
| config: const FeedsConfig( | ||
| logConfig: StreamLogConfig( | ||
| priority: StreamLogPriority.debug, | ||
| handler: StreamLogHandler.composite([ | ||
| if (kDebugMode) StreamLogConfig.defaultHandler, | ||
| StreamLogHandler.from(reportToYourCrashReporter), | ||
| ]), | ||
| ), | ||
| ), | ||
| ); | ||
| await client.connect(); | ||
| await client.dispose(); | ||
| } | ||
|
|
||
| Future<void> onlyWhileDeveloping() async { | ||
| // `kDebugMode` is what leaves a console out of the build your users run. | ||
| final client = StreamFeedsClient( | ||
| apiKey: '<your_api_key>', | ||
| user: const User(id: 'alice'), | ||
| tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')), | ||
| config: const FeedsConfig( | ||
| logConfig: StreamLogConfig( | ||
| priority: StreamLogPriority.debug, | ||
| handler: kDebugMode ? StreamLogConfig.defaultHandler : StreamLogHandler.silent, | ||
| ), | ||
| ), | ||
| ); | ||
| await client.connect(); | ||
| await client.dispose(); | ||
| } | ||
|
|
||
| Future<void> turnUpOneSubsystem() async { | ||
| // Records are tagged `SF:Ws` for the connection, `SF:Http` for the requests it makes and | ||
| // `SF:HttpAuth` for the tokens it signs them with. A filter picks out one of them, or tells this | ||
| // SDK's records apart from another Stream SDK sharing the same handler. | ||
| final client = StreamFeedsClient( | ||
| apiKey: '<your_api_key>', | ||
| user: const User(id: 'alice'), | ||
| tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')), | ||
| config: const FeedsConfig( | ||
| logConfig: StreamLogConfig( | ||
| filter: StreamLogFilter.prefix( | ||
| {'SF:Ws': StreamLogPriority.verbose}, | ||
| otherwise: StreamLogPriority.warning, | ||
| ), | ||
| ), | ||
| ), | ||
| ); | ||
| await client.connect(); | ||
| await client.dispose(); | ||
| } | ||
|
|
||
| // Placeholder for wherever your app sends its diagnostics. | ||
| void reportToYourCrashReporter(StreamLogRecord record) { | ||
| debugPrint('${record.time} ${record.priority.label}/${record.tag}: ${record.message}'); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Release client resources in each connection example.
Lines 10, 25, and 40 establish a WebSocket connection, but each function returns without calling
disconnect(). Code copied from these examples can retain the WebSocket, recovery handler, and event subscription.Call
disconnect()in afinallyblock after client use.Proposed cleanup pattern
As per coding guidelines, "Implement proper resource management with disposal and cleanup patterns in Dart code."
🤖 Prompt for AI Agents
Source: Coding guidelines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 21049a6, with
dispose()rather thandisconnect().disconnect()now only closes the connection and deliberately keeps the client usable — its subscriptions, emitters and recovery handler all stay alive — so it would not have released what this flagged.dispose()is the terminal one. The first snippet says so and points atdisconnect()for the case where the client is used again.Left out the
try/finally: these are login examples with no work in between, so the ceremony would be noise.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Length of output: 1181
🏁 Script executed:
Length of output: 4498
🏁 Script executed:
Length of output: 10578
@xsahil03x,dispose()is the correct terminal cleanup method. The implementation cancels the event subscription, closes the state emitter, disposes the recovery handler and WebSocket client, and then disposes the capabilities repository.disconnect()only closes the WebSocket and keeps the client reusable.The finding remains open because
dispose()is not called ifconnect()fails. InguestUserLogin(), it also is not called iffeed.getOrCreate()throws. Put the post-construction client use intry/finallyblocks to guarantee terminal cleanup on both paths.🐇
✏️ Learnings added
You are interacting with an AI system.