From 3e5ce584c502d98670640953bf529bc4c6e965b4 Mon Sep 17 00:00:00 2001 From: Mike Matiunin Date: Mon, 27 Jan 2025 01:44:06 +0400 Subject: [PATCH 1/4] Add headers emulation --- CHANGELOG.md | 4 + README.md | 2 + lib/src/model/command.dart | 5 + lib/src/model/config.dart | 4 +- lib/src/model/constant.dart | 6 +- lib/src/model/platform/js.dart | 2 + lib/src/model/platform/vm.dart | 2 + lib/src/model/pubspec.yaml.g.dart | 24 ++--- lib/src/model/reply.dart | 3 + lib/src/protobuf/client.pb.dart | 155 ++++++++++++++++++++++++--- lib/src/protobuf/client.pbjson.dart | 58 +++++++--- lib/src/protobuf/client.proto | 74 ++++++++----- lib/src/protobuf/protobuf_codec.dart | 2 + lib/src/spinify.dart | 2 + pubspec.yaml | 2 +- test/unit/codec_test.dart | 1 + test/unit/model_test.dart | 1 + test/unit/spinify_test.mocks.dart | 22 +--- 18 files changed, 285 insertions(+), 84 deletions(-) create mode 100644 lib/src/model/platform/js.dart create mode 100644 lib/src/model/platform/vm.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index b793721..a695fbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0 + +- Headers emulation + ## 0.1.0 - Add benchmark information to README diff --git a/README.md b/README.md index 091284f..3d87457 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ This library allows you to connect your Dart or Flutter applications to [Centrif - **Logging**: Log events, errors, and messages for debugging purposes. - **Cross-Platform**: Run on Dart VM, Flutter, and Web platforms. - **Performance**: Achieve high throughput and low latency for real-time messaging. +- **Headers Emulation**: Emulate HTTP headers for WebSocket connections at the Web platform. ## Installation @@ -180,6 +181,7 @@ _\* After message sizes exceed 15 KB, there is a noticeable performance drop._ - ✅ Benchmarks - ✅ Performance comparison with other libraries - ✅ WASM compatibility +- ✅ Headers emulation - ❌ 95% test coverage - ❌ JSON codec support for transport - ❌ DevTools extension diff --git a/lib/src/model/command.dart b/lib/src/model/command.dart index fc52680..a2d76d4 100644 --- a/lib/src/model/command.dart +++ b/lib/src/model/command.dart @@ -64,6 +64,7 @@ final class SpinifyConnectRequest extends SpinifyCommand { required this.subs, required this.name, required this.version, + required this.headers, }); @override @@ -86,6 +87,10 @@ final class SpinifyConnectRequest extends SpinifyCommand { /// Version of client. final String version; + + /// Headers to send. + /// Used for headers emulation at the web platform. + final Map? headers; } /// {@macro command} diff --git a/lib/src/model/config.dart b/lib/src/model/config.dart index c331504..a61219d 100644 --- a/lib/src/model/config.dart +++ b/lib/src/model/config.dart @@ -316,8 +316,8 @@ final class SpinifyConfig { final ({String name, String version}) client; /// Headers that are set when connecting the web socket on dart:io platforms. - /// - /// Note that headers are ignored on the web platform. + /// At the web platform, headers will be set using the headers emulation + /// with connection request. final Map headers; /// Maximum time to wait for the connection to be established. diff --git a/lib/src/model/constant.dart b/lib/src/model/constant.dart index bce04c5..7cd9bd9 100644 --- a/lib/src/model/constant.dart +++ b/lib/src/model/constant.dart @@ -1,5 +1,9 @@ +import 'platform/vm.dart' + // ignore: uri_does_not_exist + if (dart.library.js_interop) 'platform/js.dart'; + /// Whether the current platform is web. -const bool kIsWeb = identical(0, 0.0); +const bool kIsWeb = $kIsWeb; // identical(0, 0.0); /// Maximum integer value. const int kMaxInt = int.fromEnvironment( diff --git a/lib/src/model/platform/js.dart b/lib/src/model/platform/js.dart new file mode 100644 index 0000000..c262657 --- /dev/null +++ b/lib/src/model/platform/js.dart @@ -0,0 +1,2 @@ +/// Whether the current platform is web. +const bool $kIsWeb = true; diff --git a/lib/src/model/platform/vm.dart b/lib/src/model/platform/vm.dart new file mode 100644 index 0000000..b7a02e9 --- /dev/null +++ b/lib/src/model/platform/vm.dart @@ -0,0 +1,2 @@ +/// Whether the current platform is web. +const bool $kIsWeb = false; diff --git a/lib/src/model/pubspec.yaml.g.dart b/lib/src/model/pubspec.yaml.g.dart index 0d2eccd..7780de4 100644 --- a/lib/src/model/pubspec.yaml.g.dart +++ b/lib/src/model/pubspec.yaml.g.dart @@ -14,7 +14,7 @@ library pubspec; MIT License - Copyright (c) 2024 Plague Fox + Copyright (c) 2025 Plague Fox Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -93,13 +93,13 @@ sealed class Pubspec { static const PubspecVersion version = ( /// Non-canonical string representation of the version as provided /// in the pubspec.yaml file. - representation: r'0.1.0', + representation: r'0.2.0', /// Returns a 'canonicalized' representation /// of the application version. /// This represents the version string in accordance with /// Semantic Versioning (SemVer) standards. - canonical: r'0.1.0', + canonical: r'0.2.0', /// MAJOR version when you make incompatible API changes. /// The major version number: 1 in "1.2.3". @@ -108,7 +108,7 @@ sealed class Pubspec { /// MINOR version when you add functionality /// in a backward compatible manner. /// The minor version number: 2 in "1.2.3". - minor: 1, + minor: 2, /// PATCH version when you make backward compatible bug fixes. /// The patch version number: 3 in "1.2.3". @@ -123,14 +123,14 @@ sealed class Pubspec { /// Build date and time (UTC) static final DateTime timestamp = DateTime.utc( - 2024, - 11, - 28, - 18, - 51, - 31, - 283, - 52, + 2025, + 1, + 26, + 21, + 22, + 43, + 852, + 940, ); /// Name diff --git a/lib/src/model/reply.dart b/lib/src/model/reply.dart index 7676676..660e794 100644 --- a/lib/src/model/reply.dart +++ b/lib/src/model/reply.dart @@ -466,4 +466,7 @@ final class SpinifyErrorResult extends SpinifyReply /// Is error temporary. final bool temporary; + + /// Is error permanent. + bool get permanent => !temporary; } diff --git a/lib/src/protobuf/client.pb.dart b/lib/src/protobuf/client.pb.dart index bbc7d1f..df0b8e6 100644 --- a/lib/src/protobuf/client.pb.dart +++ b/lib/src/protobuf/client.pb.dart @@ -340,9 +340,9 @@ class Command extends $pb.GeneratedMessage { void clearId() => clearField(1); /// ProtocolVersion2 client can send one of the following requests. Server will - /// only take the first non-null request out of these and may return an error - /// if client passed more than one request. We are not using oneof here due to - /// JSON interoperability concerns. + /// only take the first non-null request out of these and may return an error if + /// client passed more than one request. We are not using oneof here due to JSON + /// interoperability concerns. @$pb.TagNumber(4) ConnectRequest get connect => $_getN(1); @$pb.TagNumber(4) @@ -651,8 +651,7 @@ class Reply extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearId() => clearField(1); - /// Error can only be set in replies to commands. For pushes it will have zero - /// value. + /// Error can only be set in replies to commands. For pushes it will have zero value. @$pb.TagNumber(2) Error get error => $_getN(1); @$pb.TagNumber(2) @@ -667,8 +666,8 @@ class Reply extends $pb.GeneratedMessage { @$pb.TagNumber(2) Error ensureError() => $_ensure(1); - /// ProtocolVersion2 server can send one of the following fields. We are not - /// using oneof here due to JSON interoperability concerns. + /// ProtocolVersion2 server can send one of the following fields. We are not using + /// oneof here due to JSON interoperability concerns. @$pb.TagNumber(4) Push get push => $_getN(2); @$pb.TagNumber(4) @@ -838,10 +837,9 @@ class Reply extends $pb.GeneratedMessage { SubRefreshResult ensureSubRefresh() => $_ensure(13); } -/// Push can be sent to a client as part of Reply in case of bidirectional -/// transport or without additional wrapping in case of unidirectional -/// transports. ProtocolVersion2 uses channel and one of the possible concrete -/// push messages. +/// Push can be sent to a client as part of Reply in case of bidirectional transport or +/// without additional wrapping in case of unidirectional transports. +/// ProtocolVersion2 uses channel and one of the possible concrete push messages. class Push extends $pb.GeneratedMessage { factory Push({ $core.String? channel, @@ -953,8 +951,8 @@ class Push extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearChannel() => clearField(2); - /// ProtocolVersion2 server can push one of the following fields to the client. - /// We are not using oneof here due to JSON interoperability concerns. + /// ProtocolVersion2 server can push one of the following fields to the client. We are + /// not using oneof here due to JSON interoperability concerns. @$pb.TagNumber(4) Publication get pub => $_getN(1); @$pb.TagNumber(4) @@ -1201,6 +1199,9 @@ class Publication extends $pb.GeneratedMessage { ClientInfo? info, $fixnum.Int64? offset, $core.Map<$core.String, $core.String>? tags, + $core.bool? delta, + $fixnum.Int64? time, + $core.String? channel, }) { final $result = create(); if (data != null) { @@ -1215,6 +1216,15 @@ class Publication extends $pb.GeneratedMessage { if (tags != null) { $result.tags.addAll(tags); } + if (delta != null) { + $result.delta = delta; + } + if (time != null) { + $result.time = time; + } + if (channel != null) { + $result.channel = channel; + } return $result; } Publication._() : super(); @@ -1241,6 +1251,9 @@ class Publication extends $pb.GeneratedMessage { keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OS, packageName: const $pb.PackageName('centrifugal.centrifuge.protocol')) + ..aOB(8, _omitFieldNames ? '' : 'delta') + ..aInt64(9, _omitFieldNames ? '' : 'time') + ..aOS(10, _omitFieldNames ? '' : 'channel') ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -1305,6 +1318,42 @@ class Publication extends $pb.GeneratedMessage { @$pb.TagNumber(7) $core.Map<$core.String, $core.String> get tags => $_getMap(3); + + @$pb.TagNumber(8) + $core.bool get delta => $_getBF(4); + @$pb.TagNumber(8) + set delta($core.bool v) { + $_setBool(4, v); + } + + @$pb.TagNumber(8) + $core.bool hasDelta() => $_has(4); + @$pb.TagNumber(8) + void clearDelta() => clearField(8); + + @$pb.TagNumber(9) + $fixnum.Int64 get time => $_getI64(5); + @$pb.TagNumber(9) + set time($fixnum.Int64 v) { + $_setInt64(5, v); + } + + @$pb.TagNumber(9) + $core.bool hasTime() => $_has(5); + @$pb.TagNumber(9) + void clearTime() => clearField(9); + + @$pb.TagNumber(10) + $core.String get channel => $_getSZ(6); + @$pb.TagNumber(10) + set channel($core.String v) { + $_setString(6, v); + } + + @$pb.TagNumber(10) + $core.bool hasChannel() => $_has(6); + @$pb.TagNumber(10) + void clearChannel() => clearField(10); } class Join extends $pb.GeneratedMessage { @@ -1714,6 +1763,7 @@ class Connect extends $pb.GeneratedMessage { $core.bool? pong, $core.String? session, $core.String? node, + $fixnum.Int64? time, }) { final $result = create(); if (client != null) { @@ -1746,6 +1796,9 @@ class Connect extends $pb.GeneratedMessage { if (node != null) { $result.node = node; } + if (time != null) { + $result.time = time; + } return $result; } Connect._() : super(); @@ -1778,6 +1831,7 @@ class Connect extends $pb.GeneratedMessage { ..aOB(8, _omitFieldNames ? '' : 'pong') ..aOS(9, _omitFieldNames ? '' : 'session') ..aOS(10, _omitFieldNames ? '' : 'node') + ..aInt64(11, _omitFieldNames ? '' : 'time') ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -1911,6 +1965,18 @@ class Connect extends $pb.GeneratedMessage { $core.bool hasNode() => $_has(9); @$pb.TagNumber(10) void clearNode() => clearField(10); + + @$pb.TagNumber(11) + $fixnum.Int64 get time => $_getI64(10); + @$pb.TagNumber(11) + set time($fixnum.Int64 v) { + $_setInt64(10, v); + } + + @$pb.TagNumber(11) + $core.bool hasTime() => $_has(10); + @$pb.TagNumber(11) + void clearTime() => clearField(11); } class Disconnect extends $pb.GeneratedMessage { @@ -2091,6 +2157,7 @@ class ConnectRequest extends $pb.GeneratedMessage { $core.Map<$core.String, SubscribeRequest>? subs, $core.String? name, $core.String? version, + $core.Map<$core.String, $core.String>? headers, }) { final $result = create(); if (token != null) { @@ -2108,6 +2175,9 @@ class ConnectRequest extends $pb.GeneratedMessage { if (version != null) { $result.version = version; } + if (headers != null) { + $result.headers.addAll(headers); + } return $result; } ConnectRequest._() : super(); @@ -2135,6 +2205,11 @@ class ConnectRequest extends $pb.GeneratedMessage { packageName: const $pb.PackageName('centrifugal.centrifuge.protocol')) ..aOS(4, _omitFieldNames ? '' : 'name') ..aOS(5, _omitFieldNames ? '' : 'version') + ..m<$core.String, $core.String>(6, _omitFieldNames ? '' : 'headers', + entryClassName: 'ConnectRequest.HeadersEntry', + keyFieldType: $pb.PbFieldType.OS, + valueFieldType: $pb.PbFieldType.OS, + packageName: const $pb.PackageName('centrifugal.centrifuge.protocol')) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -2210,6 +2285,9 @@ class ConnectRequest extends $pb.GeneratedMessage { $core.bool hasVersion() => $_has(4); @$pb.TagNumber(5) void clearVersion() => clearField(5); + + @$pb.TagNumber(6) + $core.Map<$core.String, $core.String> get headers => $_getMap(5); } class ConnectResult extends $pb.GeneratedMessage { @@ -2224,6 +2302,7 @@ class ConnectResult extends $pb.GeneratedMessage { $core.bool? pong, $core.String? session, $core.String? node, + $fixnum.Int64? time, }) { final $result = create(); if (client != null) { @@ -2256,6 +2335,9 @@ class ConnectResult extends $pb.GeneratedMessage { if (node != null) { $result.node = node; } + if (time != null) { + $result.time = time; + } return $result; } ConnectResult._() : super(); @@ -2288,6 +2370,7 @@ class ConnectResult extends $pb.GeneratedMessage { ..aOB(8, _omitFieldNames ? '' : 'pong') ..aOS(9, _omitFieldNames ? '' : 'session') ..aOS(10, _omitFieldNames ? '' : 'node') + ..aInt64(11, _omitFieldNames ? '' : 'time') ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -2423,6 +2506,18 @@ class ConnectResult extends $pb.GeneratedMessage { $core.bool hasNode() => $_has(9); @$pb.TagNumber(10) void clearNode() => clearField(10); + + @$pb.TagNumber(11) + $fixnum.Int64 get time => $_getI64(10); + @$pb.TagNumber(11) + set time($fixnum.Int64 v) { + $_setInt64(10, v); + } + + @$pb.TagNumber(11) + $core.bool hasTime() => $_has(10); + @$pb.TagNumber(11) + void clearTime() => clearField(11); } class RefreshRequest extends $pb.GeneratedMessage { @@ -2611,6 +2706,7 @@ class SubscribeRequest extends $pb.GeneratedMessage { $core.bool? positioned, $core.bool? recoverable, $core.bool? joinLeave, + $core.String? delta, }) { final $result = create(); if (channel != null) { @@ -2640,6 +2736,9 @@ class SubscribeRequest extends $pb.GeneratedMessage { if (joinLeave != null) { $result.joinLeave = joinLeave; } + if (delta != null) { + $result.delta = delta; + } return $result; } SubscribeRequest._() : super(); @@ -2666,6 +2765,7 @@ class SubscribeRequest extends $pb.GeneratedMessage { ..aOB(9, _omitFieldNames ? '' : 'positioned') ..aOB(10, _omitFieldNames ? '' : 'recoverable') ..aOB(11, _omitFieldNames ? '' : 'joinLeave') + ..aOS(12, _omitFieldNames ? '' : 'delta') ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -2798,6 +2898,18 @@ class SubscribeRequest extends $pb.GeneratedMessage { $core.bool hasJoinLeave() => $_has(8); @$pb.TagNumber(11) void clearJoinLeave() => clearField(11); + + @$pb.TagNumber(12) + $core.String get delta => $_getSZ(9); + @$pb.TagNumber(12) + set delta($core.String v) { + $_setString(9, v); + } + + @$pb.TagNumber(12) + $core.bool hasDelta() => $_has(9); + @$pb.TagNumber(12) + void clearDelta() => clearField(12); } class SubscribeResult extends $pb.GeneratedMessage { @@ -2812,6 +2924,7 @@ class SubscribeResult extends $pb.GeneratedMessage { $core.bool? positioned, $core.List<$core.int>? data, $core.bool? wasRecovering, + $core.bool? delta, }) { final $result = create(); if (expires != null) { @@ -2844,6 +2957,9 @@ class SubscribeResult extends $pb.GeneratedMessage { if (wasRecovering != null) { $result.wasRecovering = wasRecovering; } + if (delta != null) { + $result.delta = delta; + } return $result; } SubscribeResult._() : super(); @@ -2873,6 +2989,7 @@ class SubscribeResult extends $pb.GeneratedMessage { ..a<$core.List<$core.int>>( 11, _omitFieldNames ? '' : 'data', $pb.PbFieldType.OY) ..aOB(12, _omitFieldNames ? '' : 'wasRecovering') + ..aOB(13, _omitFieldNames ? '' : 'delta') ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -3008,6 +3125,18 @@ class SubscribeResult extends $pb.GeneratedMessage { $core.bool hasWasRecovering() => $_has(9); @$pb.TagNumber(12) void clearWasRecovering() => clearField(12); + + @$pb.TagNumber(13) + $core.bool get delta => $_getBF(10); + @$pb.TagNumber(13) + set delta($core.bool v) { + $_setBool(10, v); + } + + @$pb.TagNumber(13) + $core.bool hasDelta() => $_has(10); + @$pb.TagNumber(13) + void clearDelta() => clearField(13); } class SubRefreshRequest extends $pb.GeneratedMessage { diff --git a/lib/src/protobuf/client.pbjson.dart b/lib/src/protobuf/client.pbjson.dart index d68fb81..87ee0d3 100644 --- a/lib/src/protobuf/client.pbjson.dart +++ b/lib/src/protobuf/client.pbjson.dart @@ -446,6 +446,9 @@ const Publication$json = { '6': '.centrifugal.centrifuge.protocol.Publication.TagsEntry', '10': 'tags' }, + {'1': 'delta', '3': 8, '4': 1, '5': 8, '10': 'delta'}, + {'1': 'time', '3': 9, '4': 1, '5': 3, '10': 'time'}, + {'1': 'channel', '3': 10, '4': 1, '5': 9, '10': 'channel'}, ], '3': [Publication_TagsEntry$json], '9': [ @@ -470,8 +473,10 @@ final $typed_data.Uint8List publicationDescriptor = $convert.base64Decode( 'CgtQdWJsaWNhdGlvbhISCgRkYXRhGAQgASgMUgRkYXRhEj8KBGluZm8YBSABKAsyKy5jZW50cm' 'lmdWdhbC5jZW50cmlmdWdlLnByb3RvY29sLkNsaWVudEluZm9SBGluZm8SFgoGb2Zmc2V0GAYg' 'ASgEUgZvZmZzZXQSSgoEdGFncxgHIAMoCzI2LmNlbnRyaWZ1Z2FsLmNlbnRyaWZ1Z2UucHJvdG' - '9jb2wuUHVibGljYXRpb24uVGFnc0VudHJ5UgR0YWdzGjcKCVRhZ3NFbnRyeRIQCgNrZXkYASAB' - 'KAlSA2tleRIUCgV2YWx1ZRgCIAEoCVIFdmFsdWU6AjgBSgQIARACSgQIAhADSgQIAxAE'); + '9jb2wuUHVibGljYXRpb24uVGFnc0VudHJ5UgR0YWdzEhQKBWRlbHRhGAggASgIUgVkZWx0YRIS' + 'CgR0aW1lGAkgASgDUgR0aW1lEhgKB2NoYW5uZWwYCiABKAlSB2NoYW5uZWwaNwoJVGFnc0VudH' + 'J5EhAKA2tleRgBIAEoCVIDa2V5EhQKBXZhbHVlGAIgASgJUgV2YWx1ZToCOAFKBAgBEAJKBAgC' + 'EANKBAgDEAQ='); @$core.Deprecated('Use joinDescriptor instead') const Join$json = { @@ -585,6 +590,7 @@ const Connect$json = { {'1': 'pong', '3': 8, '4': 1, '5': 8, '10': 'pong'}, {'1': 'session', '3': 9, '4': 1, '5': 9, '10': 'session'}, {'1': 'node', '3': 10, '4': 1, '5': 9, '10': 'node'}, + {'1': 'time', '3': 11, '4': 1, '5': 3, '10': 'time'}, ], '3': [Connect_SubsEntry$json], }; @@ -613,9 +619,9 @@ final $typed_data.Uint8List connectDescriptor = $convert.base64Decode( 'dHJpZnVnZS5wcm90b2NvbC5Db25uZWN0LlN1YnNFbnRyeVIEc3VicxIYCgdleHBpcmVzGAUgAS' 'gIUgdleHBpcmVzEhAKA3R0bBgGIAEoDVIDdHRsEhIKBHBpbmcYByABKA1SBHBpbmcSEgoEcG9u' 'ZxgIIAEoCFIEcG9uZxIYCgdzZXNzaW9uGAkgASgJUgdzZXNzaW9uEhIKBG5vZGUYCiABKAlSBG' - '5vZGUaaQoJU3Vic0VudHJ5EhAKA2tleRgBIAEoCVIDa2V5EkYKBXZhbHVlGAIgASgLMjAuY2Vu' - 'dHJpZnVnYWwuY2VudHJpZnVnZS5wcm90b2NvbC5TdWJzY3JpYmVSZXN1bHRSBXZhbHVlOgI4AQ' - '=='); + '5vZGUSEgoEdGltZRgLIAEoA1IEdGltZRppCglTdWJzRW50cnkSEAoDa2V5GAEgASgJUgNrZXkS' + 'RgoFdmFsdWUYAiABKAsyMC5jZW50cmlmdWdhbC5jZW50cmlmdWdlLnByb3RvY29sLlN1YnNjcm' + 'liZVJlc3VsdFIFdmFsdWU6AjgB'); @$core.Deprecated('Use disconnectDescriptor instead') const Disconnect$json = { @@ -661,8 +667,16 @@ const ConnectRequest$json = { }, {'1': 'name', '3': 4, '4': 1, '5': 9, '10': 'name'}, {'1': 'version', '3': 5, '4': 1, '5': 9, '10': 'version'}, + { + '1': 'headers', + '3': 6, + '4': 3, + '5': 11, + '6': '.centrifugal.centrifuge.protocol.ConnectRequest.HeadersEntry', + '10': 'headers' + }, ], - '3': [ConnectRequest_SubsEntry$json], + '3': [ConnectRequest_SubsEntry$json, ConnectRequest_HeadersEntry$json], }; @$core.Deprecated('Use connectRequestDescriptor instead') @@ -682,14 +696,26 @@ const ConnectRequest_SubsEntry$json = { '7': {'7': true}, }; +@$core.Deprecated('Use connectRequestDescriptor instead') +const ConnectRequest_HeadersEntry$json = { + '1': 'HeadersEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'}, + ], + '7': {'7': true}, +}; + /// Descriptor for `ConnectRequest`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List connectRequestDescriptor = $convert.base64Decode( 'Cg5Db25uZWN0UmVxdWVzdBIUCgV0b2tlbhgBIAEoCVIFdG9rZW4SEgoEZGF0YRgCIAEoDFIEZG' 'F0YRJNCgRzdWJzGAMgAygLMjkuY2VudHJpZnVnYWwuY2VudHJpZnVnZS5wcm90b2NvbC5Db25u' 'ZWN0UmVxdWVzdC5TdWJzRW50cnlSBHN1YnMSEgoEbmFtZRgEIAEoCVIEbmFtZRIYCgd2ZXJzaW' - '9uGAUgASgJUgd2ZXJzaW9uGmoKCVN1YnNFbnRyeRIQCgNrZXkYASABKAlSA2tleRJHCgV2YWx1' - 'ZRgCIAEoCzIxLmNlbnRyaWZ1Z2FsLmNlbnRyaWZ1Z2UucHJvdG9jb2wuU3Vic2NyaWJlUmVxdW' - 'VzdFIFdmFsdWU6AjgB'); + '9uGAUgASgJUgd2ZXJzaW9uElYKB2hlYWRlcnMYBiADKAsyPC5jZW50cmlmdWdhbC5jZW50cmlm' + 'dWdlLnByb3RvY29sLkNvbm5lY3RSZXF1ZXN0LkhlYWRlcnNFbnRyeVIHaGVhZGVycxpqCglTdW' + 'JzRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSRwoFdmFsdWUYAiABKAsyMS5jZW50cmlmdWdhbC5j' + 'ZW50cmlmdWdlLnByb3RvY29sLlN1YnNjcmliZVJlcXVlc3RSBXZhbHVlOgI4ARo6CgxIZWFkZX' + 'JzRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKAlSBXZhbHVlOgI4AQ=='); @$core.Deprecated('Use connectResultDescriptor instead') const ConnectResult$json = { @@ -712,6 +738,7 @@ const ConnectResult$json = { {'1': 'pong', '3': 8, '4': 1, '5': 8, '10': 'pong'}, {'1': 'session', '3': 9, '4': 1, '5': 9, '10': 'session'}, {'1': 'node', '3': 10, '4': 1, '5': 9, '10': 'node'}, + {'1': 'time', '3': 11, '4': 1, '5': 3, '10': 'time'}, ], '3': [ConnectResult_SubsEntry$json], }; @@ -740,9 +767,9 @@ final $typed_data.Uint8List connectResultDescriptor = $convert.base64Decode( 'CgRkYXRhGAUgASgMUgRkYXRhEkwKBHN1YnMYBiADKAsyOC5jZW50cmlmdWdhbC5jZW50cmlmdW' 'dlLnByb3RvY29sLkNvbm5lY3RSZXN1bHQuU3Vic0VudHJ5UgRzdWJzEhIKBHBpbmcYByABKA1S' 'BHBpbmcSEgoEcG9uZxgIIAEoCFIEcG9uZxIYCgdzZXNzaW9uGAkgASgJUgdzZXNzaW9uEhIKBG' - '5vZGUYCiABKAlSBG5vZGUaaQoJU3Vic0VudHJ5EhAKA2tleRgBIAEoCVIDa2V5EkYKBXZhbHVl' - 'GAIgASgLMjAuY2VudHJpZnVnYWwuY2VudHJpZnVnZS5wcm90b2NvbC5TdWJzY3JpYmVSZXN1bH' - 'RSBXZhbHVlOgI4AQ=='); + '5vZGUYCiABKAlSBG5vZGUSEgoEdGltZRgLIAEoA1IEdGltZRppCglTdWJzRW50cnkSEAoDa2V5' + 'GAEgASgJUgNrZXkSRgoFdmFsdWUYAiABKAsyMC5jZW50cmlmdWdhbC5jZW50cmlmdWdlLnByb3' + 'RvY29sLlN1YnNjcmliZVJlc3VsdFIFdmFsdWU6AjgB'); @$core.Deprecated('Use refreshRequestDescriptor instead') const RefreshRequest$json = { @@ -785,6 +812,7 @@ const SubscribeRequest$json = { {'1': 'positioned', '3': 9, '4': 1, '5': 8, '10': 'positioned'}, {'1': 'recoverable', '3': 10, '4': 1, '5': 8, '10': 'recoverable'}, {'1': 'join_leave', '3': 11, '4': 1, '5': 8, '10': 'joinLeave'}, + {'1': 'delta', '3': 12, '4': 1, '5': 9, '10': 'delta'}, ], '9': [ {'1': 4, '2': 5}, @@ -798,7 +826,8 @@ final $typed_data.Uint8List subscribeRequestDescriptor = $convert.base64Decode( 'ABKAlSBXRva2VuEhgKB3JlY292ZXIYAyABKAhSB3JlY292ZXISFAoFZXBvY2gYBiABKAlSBWVw' 'b2NoEhYKBm9mZnNldBgHIAEoBFIGb2Zmc2V0EhIKBGRhdGEYCCABKAxSBGRhdGESHgoKcG9zaX' 'Rpb25lZBgJIAEoCFIKcG9zaXRpb25lZBIgCgtyZWNvdmVyYWJsZRgKIAEoCFILcmVjb3ZlcmFi' - 'bGUSHQoKam9pbl9sZWF2ZRgLIAEoCFIJam9pbkxlYXZlSgQIBBAFSgQIBRAG'); + 'bGUSHQoKam9pbl9sZWF2ZRgLIAEoCFIJam9pbkxlYXZlEhQKBWRlbHRhGAwgASgJUgVkZWx0YU' + 'oECAQQBUoECAUQBg=='); @$core.Deprecated('Use subscribeResultDescriptor instead') const SubscribeResult$json = { @@ -821,6 +850,7 @@ const SubscribeResult$json = { {'1': 'positioned', '3': 10, '4': 1, '5': 8, '10': 'positioned'}, {'1': 'data', '3': 11, '4': 1, '5': 12, '10': 'data'}, {'1': 'was_recovering', '3': 12, '4': 1, '5': 8, '10': 'wasRecovering'}, + {'1': 'delta', '3': 13, '4': 1, '5': 8, '10': 'delta'}, ], '9': [ {'1': 4, '2': 5}, @@ -836,7 +866,7 @@ final $typed_data.Uint8List subscribeResultDescriptor = $convert.base64Decode( 'JvdG9jb2wuUHVibGljYXRpb25SDHB1YmxpY2F0aW9ucxIcCglyZWNvdmVyZWQYCCABKAhSCXJl' 'Y292ZXJlZBIWCgZvZmZzZXQYCSABKARSBm9mZnNldBIeCgpwb3NpdGlvbmVkGAogASgIUgpwb3' 'NpdGlvbmVkEhIKBGRhdGEYCyABKAxSBGRhdGESJQoOd2FzX3JlY292ZXJpbmcYDCABKAhSDXdh' - 'c1JlY292ZXJpbmdKBAgEEAVKBAgFEAY='); + 'c1JlY292ZXJpbmcSFAoFZGVsdGEYDSABKAhSBWRlbHRhSgQIBBAFSgQIBRAG'); @$core.Deprecated('Use subRefreshRequestDescriptor instead') const SubRefreshRequest$json = { diff --git a/lib/src/protobuf/client.proto b/lib/src/protobuf/client.proto index 938803a..5ace50b 100644 --- a/lib/src/protobuf/client.proto +++ b/lib/src/protobuf/client.proto @@ -25,9 +25,9 @@ message Command { reserved 2, 3; // ProtocolVersion2 client can send one of the following requests. Server will - // only take the first non-null request out of these and may return an error - // if client passed more than one request. We are not using oneof here due to - // JSON interoperability concerns. + // only take the first non-null request out of these and may return an error if + // client passed more than one request. We are not using oneof here due to JSON + // interoperability concerns. ConnectRequest connect = 4; SubscribeRequest subscribe = 5; UnsubscribeRequest unsubscribe = 6; @@ -48,14 +48,13 @@ message Reply { // Id will only be set to a value > 0 for replies to commands. For pushes // it will have zero value. uint32 id = 1; - // Error can only be set in replies to commands. For pushes it will have zero - // value. + // Error can only be set in replies to commands. For pushes it will have zero value. Error error = 2; reserved 3; - // ProtocolVersion2 server can send one of the following fields. We are not - // using oneof here due to JSON interoperability concerns. + // ProtocolVersion2 server can send one of the following fields. We are not using + // oneof here due to JSON interoperability concerns. Push push = 4; ConnectResult connect = 5; SubscribeResult subscribe = 6; @@ -70,16 +69,15 @@ message Reply { SubRefreshResult sub_refresh = 15; } -// Push can be sent to a client as part of Reply in case of bidirectional -// transport or without additional wrapping in case of unidirectional -// transports. ProtocolVersion2 uses channel and one of the possible concrete -// push messages. +// Push can be sent to a client as part of Reply in case of bidirectional transport or +// without additional wrapping in case of unidirectional transports. +// ProtocolVersion2 uses channel and one of the possible concrete push messages. message Push { reserved 1, 3; string channel = 2; - // ProtocolVersion2 server can push one of the following fields to the client. - // We are not using oneof here due to JSON interoperability concerns. + // ProtocolVersion2 server can push one of the following fields to the client. We are + // not using oneof here due to JSON interoperability concerns. Publication pub = 4; Join join = 5; Leave leave = 6; @@ -104,11 +102,18 @@ message Publication { ClientInfo info = 5; uint64 offset = 6; map tags = 7; + bool delta = 8; // When set indicates that data in Publication is a delta from previous data. + int64 time = 9; // Optional time of publication as Unix timestamp milliseconds. + string channel = 10; // Optional channel name if Publication relates to wildcard subscription. } -message Join { ClientInfo info = 1; } +message Join { + ClientInfo info = 1; +} -message Leave { ClientInfo info = 1; } +message Leave { + ClientInfo info = 1; +} message Unsubscribe { reserved 1; @@ -125,7 +130,9 @@ message Subscribe { bytes data = 7; } -message Message { bytes data = 1; } +message Message { + bytes data = 1; +} message Connect { string client = 1; @@ -138,6 +145,7 @@ message Connect { bool pong = 8; string session = 9; string node = 10; + int64 time = 11; // Server time as Unix timestamp in milliseconds (not sent by default). } message Disconnect { @@ -157,6 +165,7 @@ message ConnectRequest { map subs = 3; string name = 4; string version = 5; + map headers = 6; } message ConnectResult { @@ -170,9 +179,12 @@ message ConnectResult { bool pong = 8; string session = 9; string node = 10; + int64 time = 11; // Server time as Unix timestamp in milliseconds (not sent by default). } -message RefreshRequest { string token = 1; } +message RefreshRequest { + string token = 1; +} message RefreshResult { string client = 1; @@ -192,6 +204,7 @@ message SubscribeRequest { bool positioned = 9; bool recoverable = 10; bool join_leave = 11; + string delta = 12; } message SubscribeResult { @@ -206,6 +219,7 @@ message SubscribeResult { bool positioned = 10; bytes data = 11; bool was_recovering = 12; + bool delta = 13; } message SubRefreshRequest { @@ -218,7 +232,9 @@ message SubRefreshResult { uint32 ttl = 2; } -message UnsubscribeRequest { string channel = 1; } +message UnsubscribeRequest { + string channel = 1; +} message UnsubscribeResult {} @@ -229,11 +245,17 @@ message PublishRequest { message PublishResult {} -message PresenceRequest { string channel = 1; } +message PresenceRequest { + string channel = 1; +} -message PresenceResult { map presence = 1; } +message PresenceResult { + map presence = 1; +} -message PresenceStatsRequest { string channel = 1; } +message PresenceStatsRequest { + string channel = 1; +} message PresenceStatsResult { uint32 num_clients = 1; @@ -263,11 +285,15 @@ message PingRequest {} message PingResult {} -message RPCRequest { +message RPCRequest{ bytes data = 1; string method = 2; } -message RPCResult { bytes data = 1; } +message RPCResult { + bytes data = 1 ; +} -message SendRequest { bytes data = 1; } \ No newline at end of file +message SendRequest{ + bytes data = 1; +} \ No newline at end of file diff --git a/lib/src/protobuf/protobuf_codec.dart b/lib/src/protobuf/protobuf_codec.dart index da3f747..d9dbda9 100644 --- a/lib/src/protobuf/protobuf_codec.dart +++ b/lib/src/protobuf/protobuf_codec.dart @@ -91,6 +91,8 @@ final class SpinifyProtobufCommandEncoder }, name: connect.name, version: connect.version, + // Headers emulation at the web platform. + headers: connect.headers, ); case SpinifySubscribeRequest subscribe: cmd.subscribe = pb.SubscribeRequest( diff --git a/lib/src/spinify.dart b/lib/src/spinify.dart index 9375243..63944c1 100644 --- a/lib/src/spinify.dart +++ b/lib/src/spinify.dart @@ -785,6 +785,8 @@ final class Spinify implements ISpinify { }, name: config.client.name, version: config.client.version, + headers: + kIsWeb && config.headers.isNotEmpty ? config.headers : null, ); } diff --git a/pubspec.yaml b/pubspec.yaml index 960c9ce..dd2f6a9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ description: > Dart client to communicate with Centrifuge and Centrifugo from Dart and Flutter over WebSockets with Protobuf support. -version: 0.1.0 +version: 0.2.0 homepage: https://centrifugal.dev diff --git a/test/unit/codec_test.dart b/test/unit/codec_test.dart index fd7f12b..bbe1c74 100644 --- a/test/unit/codec_test.dart +++ b/test/unit/codec_test.dart @@ -55,6 +55,7 @@ void main() => group('Codec', () { token: 'token', ), }, + headers: const {'X-Key': 'Value'}, ), SpinifySubscribeRequest( channel: 'channel', diff --git a/test/unit/model_test.dart b/test/unit/model_test.dart index aaafd3e..42ad3d4 100644 --- a/test/unit/model_test.dart +++ b/test/unit/model_test.dart @@ -778,6 +778,7 @@ void main() { token: token, ), }, + headers: const {'X-Key': 'Value'}, ), command.SpinifySubscribeRequest( channel: channel, diff --git a/test/unit/spinify_test.mocks.dart b/test/unit/spinify_test.mocks.dart index 9616d8e..9adc394 100644 --- a/test/unit/spinify_test.mocks.dart +++ b/test/unit/spinify_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.4 from annotations +// Mocks generated by Mockito 5.4.5 from annotations // in spinify/test/unit/spinify_test.dart. // Do not manually edit this file. @@ -16,6 +16,7 @@ import 'package:spinify/src/model/transport_interface.dart' as _i2; // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types @@ -41,26 +42,13 @@ class MockWebSocket extends _i1.Mock implements _i2.WebSocket { @override void add(List? data) => super.noSuchMethod( - Invocation.method( - #add, - [data], - ), + Invocation.method(#add, [data]), returnValueForMissingStub: null, ); @override - void close([ - int? code, - String? reason, - ]) => - super.noSuchMethod( - Invocation.method( - #close, - [ - code, - reason, - ], - ), + void close([int? code, String? reason]) => super.noSuchMethod( + Invocation.method(#close, [code, reason]), returnValueForMissingStub: null, ); } From 58a48a9fa42e61bcd80886a7de26f58d96f1df1e Mon Sep 17 00:00:00 2001 From: Mike Matiunin Date: Mon, 27 Jan 2025 01:47:21 +0400 Subject: [PATCH 2/4] Update centrifuge at benchmark --- example/benchmark/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/benchmark/pubspec.yaml b/example/benchmark/pubspec.yaml index 056cad2..1b7c102 100644 --- a/example/benchmark/pubspec.yaml +++ b/example/benchmark/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: cupertino_icons: ^1.0.8 # Centrifuge clients - centrifuge: ^0.14.0 + centrifuge: ^0.15.0 spinify: path: ../../ From 0038d3abfa594f01f427c80e64977af16e6b89d0 Mon Sep 17 00:00:00 2001 From: Plague Fox Date: Mon, 27 Jan 2025 01:57:58 +0400 Subject: [PATCH 3/4] Update web benhmarks --- README.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3d87457..c626dcb 100644 --- a/README.md +++ b/README.md @@ -122,14 +122,8 @@ Each message is sent sequentially: the client waits for the server's response be | | Spinify | Centrifuge-Dart | | ----- | ------------------- | ------------------- | | 1 KB | 5763 msg/s (7MB/s) | 5361 msg/s (6MB/s) | -| 5 KB | 4405 msg/s (22MB/s) | 3731 msg/s (18MB/s) | -| 10 KB | 3717 msg/s (37MB/s) | 2857 msg/s (28MB/s) | | 14 KB | 3305 msg/s (45MB/s) | 2564 msg/s (35MB/s) | -| 16 KB | 3091 msg/s (50MB/s) | 1982 msg/s (32MB/s) | -| 20 KB | 2812 msg/s (56MB/s) | 1811 msg/s (36MB/s) | | 30 KB | 2463 msg/s (72MB/s) | 1470 msg/s (43MB/s) | -| 40 KB | 1937 msg/s (76MB/s) | 1089 msg/s (42MB/s) | -| 50 KB | 1740 msg/s (85MB/s) | 967 msg/s (47MB/s) | | 60 KB | 1583 msg/s (92MB/s) | 877 msg/s (51MB/s) | _\* Messages larger than 64 KB are not supported._ @@ -138,11 +132,10 @@ _\* Messages larger than 64 KB are not supported._ | | Spinify WASM | Spinify JS | Centrifuge-Dart JS | | ----- | ------------------- | ------------------- | ------------------- | -| 1 KB | 3676 msg/s (4MB/s) | 3502 msg/s (4MB/s) | 3067 msg/s (3MB/s) | -| 5 KB | 2659 msg/s (13MB/s) | 3484 msg/s (17MB/s) | 2207 msg/s (11MB/s) | -| 10 KB | 1926 msg/s (19MB/s) | 3189 msg/s (31MB/s) | 1584 msg/s (15MB/s) | -| 14 KB | 1670 msg/s (22MB/s) | 2890 msg/s (39MB/s) | 1287 msg/s (17MB/s) | -| 16 KB | 39 msg/s (662KB/s) | 39 msg/s (662KB/s) | 39 msg/s (662KB/s) | +| 1 KB | 3676 msg/s (4MB/s) | 3590 msg/s (6MB/s) | 3720 msg/s (6MB/s) | +| 5 KB | 2659 msg/s (13MB/s) | 3227 msg/s (18MB/s) | 3223 msg/s (18MB/s) | +| 10 KB | 1926 msg/s (19MB/s) | 3031 msg/s (32MB/s) | 3029 msg/s (32MB/s) | +| 14 KB | 1670 msg/s (22MB/s) | 2750 msg/s (39MB/s) | 2830 msg/s (40MB/s) | _\* After message sizes exceed 15 KB, there is a noticeable performance drop._ From c7bb278b16f6d37dda90ffdde4c2cb9fddad17aa Mon Sep 17 00:00:00 2001 From: Plague Fox Date: Mon, 27 Jan 2025 02:03:57 +0400 Subject: [PATCH 4/4] Update benchmark --- README.md | 8 +- example/benchmark/.gitignore | 2 + example/benchmark/.metadata | 30 ++-- example/benchmark/linux/CMakeLists.txt | 23 +--- example/benchmark/linux/runner/CMakeLists.txt | 26 ++++ example/benchmark/linux/runner/main.cc | 6 + .../benchmark/linux/runner/my_application.cc | 130 ++++++++++++++++++ .../benchmark/linux/runner/my_application.h | 18 +++ .../benchmark/macos/Runner/AppDelegate.swift | 4 + .../macos/Runner/Configs/AppInfo.xcconfig | 2 +- example/benchmark/windows/runner/Runner.rc | 2 +- 11 files changed, 210 insertions(+), 41 deletions(-) create mode 100644 example/benchmark/linux/runner/CMakeLists.txt create mode 100644 example/benchmark/linux/runner/main.cc create mode 100644 example/benchmark/linux/runner/my_application.cc create mode 100644 example/benchmark/linux/runner/my_application.h diff --git a/README.md b/README.md index c626dcb..621468d 100644 --- a/README.md +++ b/README.md @@ -121,10 +121,10 @@ Each message is sent sequentially: the client waits for the server's response be | | Spinify | Centrifuge-Dart | | ----- | ------------------- | ------------------- | -| 1 KB | 5763 msg/s (7MB/s) | 5361 msg/s (6MB/s) | -| 14 KB | 3305 msg/s (45MB/s) | 2564 msg/s (35MB/s) | -| 30 KB | 2463 msg/s (72MB/s) | 1470 msg/s (43MB/s) | -| 60 KB | 1583 msg/s (92MB/s) | 877 msg/s (51MB/s) | +| 1 KB | 5396 msg/s (6MB/s) | 5433 msg/s (6MB/s) | +| 14 KB | 3216 msg/s (46MB/s) | 3224 msg/s (46MB/s) | +| 30 KB | 2371 msg/s (71MB/s) | 2352 msg/s (70MB/s) | +| 60 KB | 1558 msg/s (92MB/s) | 1547 msg/s (91MB/s) | _\* Messages larger than 64 KB are not supported._ diff --git a/example/benchmark/.gitignore b/example/benchmark/.gitignore index 29a3a50..79c113f 100644 --- a/example/benchmark/.gitignore +++ b/example/benchmark/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/example/benchmark/.metadata b/example/benchmark/.metadata index 2d1be89..1f19d86 100644 --- a/example/benchmark/.metadata +++ b/example/benchmark/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "2663184aa79047d0a33a14a3b607954f8fdd8730" + revision: "c519ee916eaeb88923e67befb89c0f1dabfa83e6" channel: "stable" project_type: app @@ -13,26 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 + base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 - platform: android - create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 + base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 - platform: ios - create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 + base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 - platform: linux - create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 + base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 - platform: macos - create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 + base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 - platform: web - create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 + base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 - platform: windows - create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 + base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 # User provided section diff --git a/example/benchmark/linux/CMakeLists.txt b/example/benchmark/linux/CMakeLists.txt index b9a4b4f..c89152f 100644 --- a/example/benchmark/linux/CMakeLists.txt +++ b/example/benchmark/linux/CMakeLists.txt @@ -1,5 +1,5 @@ # Project-level configuration. -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change @@ -54,25 +54,8 @@ add_subdirectory(${FLUTTER_MANAGED_DIR}) find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) -add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") - -# Define the application target. To change its name, change BINARY_NAME above, -# not the value here, or `flutter run` will no longer work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} - "main.cc" - "my_application.cc" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add dependency libraries. Add any application-specific dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter) -target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/example/benchmark/linux/runner/CMakeLists.txt b/example/benchmark/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/example/benchmark/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/example/benchmark/linux/runner/main.cc b/example/benchmark/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/example/benchmark/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/example/benchmark/linux/runner/my_application.cc b/example/benchmark/linux/runner/my_application.cc new file mode 100644 index 0000000..bd52eaf --- /dev/null +++ b/example/benchmark/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "spinifybenchmark"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "spinifybenchmark"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/example/benchmark/linux/runner/my_application.h b/example/benchmark/linux/runner/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/example/benchmark/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/example/benchmark/macos/Runner/AppDelegate.swift b/example/benchmark/macos/Runner/AppDelegate.swift index 8e02df2..b3c1761 100644 --- a/example/benchmark/macos/Runner/AppDelegate.swift +++ b/example/benchmark/macos/Runner/AppDelegate.swift @@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/example/benchmark/macos/Runner/Configs/AppInfo.xcconfig b/example/benchmark/macos/Runner/Configs/AppInfo.xcconfig index 3a326b0..7751b30 100644 --- a/example/benchmark/macos/Runner/Configs/AppInfo.xcconfig +++ b/example/benchmark/macos/Runner/Configs/AppInfo.xcconfig @@ -11,4 +11,4 @@ PRODUCT_NAME = spinifybenchmark PRODUCT_BUNDLE_IDENTIFIER = dev.plugfox.spinifybenchmark // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2024 dev.plugfox. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 dev.plugfox. All rights reserved. diff --git a/example/benchmark/windows/runner/Runner.rc b/example/benchmark/windows/runner/Runner.rc index a83c43e..77b3f0a 100644 --- a/example/benchmark/windows/runner/Runner.rc +++ b/example/benchmark/windows/runner/Runner.rc @@ -93,7 +93,7 @@ BEGIN VALUE "FileDescription", "spinifybenchmark" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "spinifybenchmark" "\0" - VALUE "LegalCopyright", "Copyright (C) 2024 dev.plugfox. All rights reserved." "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 dev.plugfox. All rights reserved." "\0" VALUE "OriginalFilename", "spinifybenchmark.exe" "\0" VALUE "ProductName", "spinifybenchmark" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0"