-
Notifications
You must be signed in to change notification settings - Fork 306
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
api: Don't allow connecting to servers <4.0 #1410
Conversation
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.
Thanks for the PR! I went through all the commits and left some comments. This will need a rebase because of some conflicts with #1400.
lib/model/store.dart
Outdated
/// The fields 'zulip_version', 'zulip_merge_base', and 'zulip_feature_level' | ||
/// from a /register response. | ||
class ZulipVersionData { | ||
ZulipVersionData({ |
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.
nit: This can be a const
constructor.
/// | ||
/// In English, the text in [successContent] should be short, should start with | ||
/// a capital letter, and should have no ending punctuation: "{noun} copied". | ||
static void copyWithPopup({ |
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.
I looked around for other methods to be moved here, and found ShareButton.onPressed
and _uploadFiles
from lib/widgets/compose_box.dart
to fit conceptually, but neither of them needs to be reused now or in the near future. So I think the current collection of methods in PlatformActions
is pretty complete.
final zulipLocalizations = ZulipLocalizations.of(context); | ||
showErrorDialog(context: context, | ||
title: zulipLocalizations.errorCouldNotOpenLinkTitle, | ||
message: zulipLocalizations.errorCouldNotOpenLink(urlString)); |
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.
nit: While the main motivation from fe3538b does apply here, would it make sense to use url.toString
here to be consistent?
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.
While we do have a test "error dialog if invalid link" that covers a similar error in PlatformActions.launchUrl
, I saw that this branch does not have test coverage.
We should probably move that test to the corresponding file for PlatformActions
.
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.
nit: While the main motivation from fe3538b does apply here, would it make sense to use
url.toString
here to be consistent?
No, not here :) because this is in an if (url == null) {}
, and url.toString()
would always be "null".
@@ -343,6 +343,9 @@ class _LoginPageState extends State<LoginPage> { | |||
// Could set [_inProgress]… but we'd need to unset it if the web-auth | |||
// attempt is aborted (by the user closing the browser, for example), | |||
// and I don't think we can reliably know when that happens. | |||
|
|||
// Not using [PlatformActions.launchUrl] because web auth needs special | |||
// error handling. |
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.
Perhaps as a part of #462, we can get rid of the block of iOS specific error handling code and reuse PlatofmActions.launchUrl
, but I'm hesitant because of the question mark from // TODO(#462) remove this?
.
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.
Yeah; that sounds like it would need investigating when we get to #462.
lib/model/store.dart
Outdated
zulipMergeBase: initialSnapshot.zulipMergeBase, | ||
zulipFeatureLevel: initialSnapshot.zulipFeatureLevel); | ||
|
||
/// A [ZulipVersionData] from a [MalformedServerResponseException], |
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.
I think it might be better to start this with a verb.
async.flushTimers(); | ||
// Reload never succeeds and there are no unhandled errors. | ||
check(globalStore.perAccountSync(eg.selfAccount.id)).isNull(); | ||
})); |
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.
Other than these tests, I think it would be valuable to have an end-to-end widget test that checks the error dialog when connecting to an ancient server, but I feel like it can be difficult to set up.
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.
I agree. We'd need change TestZulipBinding
so that it could work with a UpdateMachineTestGlobalStore
, and I'm not sure how best to do that.
The TestZulipBinding.globalStore
getter has a TestGlobalStore
return type, and UpdateMachineTestGlobalStore
doesn't extend TestGlobalStore
. Both classes extend GlobalStore
, but that doesn't make sense as a return type; it would break a lot of callers that use things like TestGlobalStore.add
.
Thanks Zixuan for noticing we haven't been covering this: zulip#1410 (comment)
Thanks Zixuan for noticing we haven't been covering this: zulip#1410 (comment)
b38efb2
to
4188181
Compare
Thanks for the review! Revision pushed. |
4188181
to
1ef8b60
Compare
(Just rebased past #1416.) |
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.
Thanks for the update! I read the new commit and have some small comments. Marking this for Greg's review.
/// See also: | ||
/// * [launchUrlResult] | ||
/// * [takeLaunchUrlCalls] | ||
PlatformException? launchUrlException; |
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.
Looks like this should be reset in _resetLaunchUrl
.
lib/api/core.dart
Outdated
/// and the README. | ||
const kMinSupportedZulipVersion = '4.0'; | ||
|
||
/// The Zulip feature level reserved for the [minSupportedZulipVersion] release. |
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.
nit: the only minSupportedZulipVersion
I can find is the parameter of errorServerVersionUnsupportedMessage
, but it is not a defined symbol within the dartdoc's context; I think this is supposed to be the "k"-prefixed constant?
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.
Indeed, thanks for the catch!
lib/widgets/actions.dart
Outdated
} | ||
|
||
/// Opens a URL with [ZulipBinding.launchUrl], with an error dialog on failure. | ||
// TODO widget tests |
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.
Now that we have a dedicated test group, we should be able to remove the TODO, unless there is something else that needs tests here?
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.
Indeed!
1ef8b60
to
2468db2
Compare
Thanks for the review! Revision pushed. |
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.
Thanks! I've read most of the branch:
e13084a store test [nfc]: Replace old method name in a test group
8228d97 store [nfc]: Move some declarations up out of a switch case
ebe5f70 store [nfc]: Re-query account after _registerQueueWithRetry async gap
2983cdb store [nfc]: Helper code for UpdateMachine.load's update-feature-level
413bf1b store [nfc]: Stop using mutable Account variable in UpdateMachine.load
fb73e41 content [nfc]: Inline showError helper in _launchUrl
50379e2 actions test [nfc]: Put ZulipActions tests in new 'ZulipActions' group
b03c30a actions [nfc]: Add PlatformActions, alongside ZulipActions
ae8a3f7 content: Use url.toString()
instead of urlString
in error message
eb9883f actions [nfc]: Move open-link logic here, from lib/widgets/content
and part of the final/main commit:
2468db2 api: Don't allow connecting to servers <4.0
That leaves:
367a165 actions test: Cover PlatformActions.launchUrl
ad292ca dialog: Add "Learn more"-button param to showErrorDialog
and the rest of the final commit.
test/model/store_test.dart
Outdated
group('GlobalStore.updateGlobalSettings', () { | ||
group('GlobalStore update settings', () { |
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.
This looks like it was meant to happen in 9201ae42f.
Yeah. #1421 has its own version of this fix.
lib/model/store.dart
Outdated
connection.zulipFeatureLevel = initialSnapshot.zulipFeatureLevel; | ||
} | ||
final zulipVersionData = ZulipVersionData.fromInitialSnapshot(initialSnapshot); | ||
await updateZulipVersionData(zulipVersionData); |
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.
I don't love having this introduce an await when we usually don't need one (because the version data will usually be up to date already).
How about introducing a method to consolidate that version-comparison condition? Then this can look like
if (!zulipVersionData.matchesAccount(globalStore.getAccount(accountId)!)) {
// …
}
lib/model/store.dart
Outdated
connection.close(); | ||
await updateZulipVersionData(e.data); |
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.
This will mutate connection
after closing it. Probably doesn't throw, but incorrect in principle.
With the suggestion in the previous comment, this can inline updateZulipVersionData
, and just skip the line that updates connection
.
Maybe further clean up by having a method on GlobalStore
:
if (!zulipVersionData.matchesAccount(globalStore.getAccount(accountId)!)) {
await globalStore.updateZulipVersionData(accountId, zulipVersionData);
}
lib/model/store.dart
Outdated
return ZulipVersionData( | ||
zulipVersion: data['zulip_version'] as String, | ||
zulipMergeBase: data['zulip_merge_base'] as String?, | ||
zulipFeatureLevel: data['zulip_feature_level'] as int); |
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.
If there's a zulip_version
but no zulip_feature_level
, I think we can infer it's indeed Zulip just ancient — so the feature level is zero. (I.e. it's from before Zulip 3.0 introduced feature levels.)
2468db2
to
3297253
Compare
Thanks for the review! Revision pushed. |
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.
Thanks! Looks good, and I've now read the whole thing. Just a few small comments below.
test/widgets/actions_test.dart
Outdated
final httpUrl = Uri.parse('http://chat.zulip.org'); | ||
final nonHttpUrl = Uri.parse('mailto:[email protected]'); |
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.
nit:
final httpUrl = Uri.parse('http://chat.zulip.org'); | |
final nonHttpUrl = Uri.parse('mailto:chat@zulip.org'); | |
final httpUrl = Uri.parse('https://chat.example'); | |
final nonHttpUrl = Uri.parse('mailto:chat@example'); |
- use reserved example domains (RFC 6761; https://en.wikipedia.org/wiki/Special-use_domain_name)
- use
https:
rather thanhttp:
, sincehttps:
should be the norm and cleartexthttp:
rare
test/widgets/dialog_test.dart
Outdated
final element = tester.element(find.byType(Placeholder)); | ||
|
||
showErrorDialog(context: element, title: 'hello', | ||
learnMoreButtonUrl: Uri.parse('foo.example')); |
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.
nit: make it a proper absolute URL, for realism
learnMoreButtonUrl: Uri.parse('foo.example')); | |
learnMoreButtonUrl: Uri.parse('https://foo.example')); |
(if it isn't, launching is unlikely to work)
// `!` is OK because _registerQueueWithRetry would have thrown a | ||
// not-_ServerVersionUnsupportedException if no account | ||
if (!e.data.matchesAccount(globalStore.getAccount(accountId)!)) { | ||
await globalStore.updateZulipVersionData(accountId, e.data); |
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.
I guess since the caller is going to log the account out anyway, this update isn't really needed. (Realized that when reading tests and seeing there doesn't seem to be a test exercising this.)
Having already done the work to write it, though, may as well keep it. It's more correct in principle, and will simplify if in the future we have it not log out in this case.
lib/api/core.dart
Outdated
// TODO: Instead, link to new Help Center doc once we have it: | ||
// https://github.com/zulip/zulip/issues/23842 | ||
final kServerSupportDocUrl = Uri.parse( | ||
'https://zulip.readthedocs.io/en/latest/overview/release-lifecycle.html#compatibility-and-upgrading'); |
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.
'https://zulip.readthedocs.io/en/latest/overview/release-lifecycle.html#compatibility-and-upgrading'); | |
'https://zulip.readthedocs.io/en/latest/overview/release-lifecycle.html#client-apps'); |
The other URL's fragment doesn't seem to exist on that page now.
(It's fine, the page still loads, but the user doesn't get taken to any particular spot on it.)
3297253
to
85f8829
Compare
Thanks for the review! Revision pushed. |
And rethrow AccountNotFoundExceptions in an explicit `on` block, rather than in the `default` case of this `switch`. (Otherwise the account-exists check would throw.)
I believe this is NFC because: - the only fields we read are zulipVersion, zulipMergeBase, and zulipFeatureLevel - we read those fields only to update them if needed - nothing else could have written to those fields during the async gap; this register-queue codepath is the only codepath that writes to them, and we don't allow concurrent register-queues for an account But we've been using an Account object from across an async gap, and that looks like a code smell that calls for a fix.
This will be helpful for disallowing ancient servers, coming up.
Some of _launchUrl is specific to opening links in message content, but a lot is generic and could helpfully be pulled out to be reused in other places where we open links. We'll do that next; first, inline this helper function, which has one caller in the content-specific code and one in the generic code.
And bring over one function that fits well here. This seems like a good place to put a function for opening a link; we'll do that next.
Just so when we extract out much of this code for reuse, it won't need a `urlString` param alongside `url`. I think the effect is just that when the URL is a relative URL, the error dialog will always show it as an absolute URL on the realm.
We'll use when we add a "Learn more"-button param to showErrorDialog, coming up.
And reportErrorToUserModally.
From the README: https://github.com/zulip/zulip-flutter?tab=readme-ov-file#server-compatibility > We support Zulip Server 4.0 and later. This implementation isn't ideal because it logs you out, instead of just taking down the account's UI as we discussed: https://chat.zulip.org/#narrow/channel/243-mobile-team/topic/.23F267.20Disallow.20connecting.20to.20unsupported.20ancient.20servers/near/2104182 But it was simpler this way since programmatically logging out is already an action we handle. Fixes: zulip#267
Thanks! Looks good; merging. Glad to have this complete. 🙂 |
85f8829
to
0494723
Compare
From the README:
https://github.com/zulip/zulip-flutter?tab=readme-ov-file#server-compatibility
This implementation isn't ideal because it logs you out, instead of
just taking down the account's UI as we discussed:
https://chat.zulip.org/#narrow/channel/243-mobile-team/topic/.23F267.20Disallow.20connecting.20to.20unsupported.20ancient.20servers/near/2104182
But it was simpler this way since programmatically logging out is
already an action we handle.
Fixes: #267
Screenshot (with
kMinSupportedZulipFeatureLevel
fudged to 9999):