diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml
new file mode 100644
index 00000000..6f945fc8
--- /dev/null
+++ b/.github/workflows/check.yaml
@@ -0,0 +1,97 @@
+name: Check
+
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - main
+jobs:
+ lint:
+ runs-on: macos-latest
+
+ # From actions/cache documentation linked to below
+ env:
+ MINT_PATH: .mint/lib
+ MINT_LINK_PATH: .mint/bin
+
+ steps:
+ - uses: actions/checkout@v4
+
+ # https://github.com/actions/cache/blob/40c3b67b2955d93d83b27ed164edd0756bc24049/examples.md#swift---mint
+ - uses: actions/cache@v4
+ with:
+ path: .mint
+ key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }}
+ restore-keys: |
+ ${{ runner.os }}-mint-
+
+ - run: npm ci
+ - run: brew install mint
+ - run: mint bootstrap
+
+ - run: script/format/check
+ - run: script/lint/check
+
+ generate-matrices:
+ runs-on: macos-latest
+ outputs:
+ matrix: ${{ steps.generation-step.outputs.matrix }}
+ steps:
+ - uses: actions/checkout@v4
+ - id: generation-step
+ run: swift run BuildTool generate-matrices >> $GITHUB_OUTPUT
+
+ check-spm:
+ name: SPM (Xcode ${{ matrix.tooling.xcode-version }}, Swift ${{ matrix.tooling.swift-version }})
+ runs-on: macos-latest
+ needs: generate-matrices
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(needs.generate-matrices.outputs.matrix) }}
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: ${{ matrix.tooling.xcode-version }}
+
+ # https://forums.swift.org/t/warnings-as-errors-for-libraries-frameworks/58393/2
+ - run: swift build -Xswiftc -warnings-as-errors -Xswiftc -swift-version -Xswiftc ${{ matrix.tooling.swift-version }}
+ - run: swift test -Xswiftc -warnings-as-errors -Xswiftc -swift-version -Xswiftc ${{ matrix.tooling.swift-version }}
+
+ check-xcode:
+ name: Xcode, ${{matrix.platform}} (Xcode ${{ matrix.tooling.xcode-version }}, Swift ${{ matrix.tooling.swift-version }})
+ runs-on: macos-latest
+ needs: generate-matrices
+
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(needs.generate-matrices.outputs.matrix) }}
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: ${{ matrix.tooling.xcode-version }}
+
+ - name: Build and run tests
+ run: swift run BuildTool build-and-test-library --platform ${{ matrix.platform }} --swift-version ${{ matrix.tooling.swift-version }}
+
+ check-example-app:
+ name: Example app, ${{matrix.platform}} (Xcode ${{ matrix.tooling.xcode-version }}, Swift ${{ matrix.tooling.swift-version }})
+ runs-on: macos-latest
+ needs: generate-matrices
+
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(needs.generate-matrices.outputs.matrix) }}
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: ${{ matrix.tooling.xcode-version }}
+
+ - name: Build example app
+ run: swift run BuildTool build-example-app --platform ${{ matrix.platform }} --swift-version ${{ matrix.tooling.swift-version }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..7d6a7766
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+# Start of .gitignore created by Swift Package Manager
+.DS_Store
+/.build
+/Packages
+xcuserdata/
+DerivedData/
+.swiftpm/configuration/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc
+# End of .gitignore created by Swift Package Manager
+
+/node_modules
+/.mint
+
+# Don’t try and format the asset catalogue JSON files which are managed by Xcode
+*.xcassets/
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..1443d6cc
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+# Don’t try and format the asset catalogue JSON files, which are managed by Xcode
+*.xcassets/
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1 @@
+{}
diff --git a/.swift-version b/.swift-version
new file mode 100644
index 00000000..f9ce5a96
--- /dev/null
+++ b/.swift-version
@@ -0,0 +1 @@
+5.10
diff --git a/.swiftlint.yml b/.swiftlint.yml
new file mode 100644
index 00000000..ac63287c
--- /dev/null
+++ b/.swiftlint.yml
@@ -0,0 +1,102 @@
+excluded:
+ - .build
+
+strict: true
+
+disabled_rules:
+ # All of the default rules of type "metrics". We have no reason to believe that the arbitrary defaults picked by SwiftLint are helpful.
+ - cyclomatic_complexity
+ - file_length
+ - function_body_length
+ - function_parameter_count
+ - large_tuple
+ - line_length
+ - nesting
+ - type_body_length
+
+ # Rules of type "lint" that we’ve decided we don’t want:
+ - todo # We frequently use TODOs accompanied by a GitHub issue reference
+
+opt_in_rules:
+ # All of the opt-in rules of type "performance":
+ - contains_over_filter_count
+ - contains_over_filter_is_empty
+ - contains_over_first_not_nil
+ - contains_over_range_nil_comparison
+ - empty_collection_literal
+ - empty_count
+ - empty_string
+ - first_where
+ - flatmap_over_map_reduce
+ - last_where
+ - reduce_into
+ - sorted_first_last
+
+ # Opt-in rules of type "style" that we’ve decided we want:
+ - attributes
+ - closure_end_indentation
+ - closure_spacing
+ - collection_alignment
+ - comma_inheritance
+ - conditional_returns_on_newline
+ - file_header
+ - implicit_return
+ - literal_expression_end_indentation
+ - modifier_order
+ - multiline_arguments
+ - multiline_arguments_brackets
+ - multiline_function_chains
+ - multiline_literal_brackets
+ - multiline_parameters
+ - multiline_parameters_brackets
+ - operator_usage_whitespace
+ - prefer_self_type_over_type_of_self
+ - self_binding
+ - single_test_class
+ - sorted_imports
+ - switch_case_on_newline
+ - trailing_closure
+ - trailing_newline
+ - unneeded_parentheses_in_closure_argument
+ - vertical_parameter_alignment_on_call
+ - vertical_whitespace_closing_braces
+ - vertical_whitespace_opening_braces
+
+ # Opt-in rules of type "idiomatic" that we’ve decided we want:
+ - anonymous_argument_in_multiline_closure
+ - convenience_type
+ - fallthrough
+ - fatal_error_message
+ - pattern_matching_keywords
+ - redundant_type_annotation
+ - shorthand_optional_binding
+ - static_operator
+ - toggle_bool
+ - xct_specific_matcher
+
+ # Opt-in rules of type "lint" that we’ve decided we want:
+ - array_init
+ - empty_xctest_method
+ - missing_docs
+ - override_in_extension
+ - yoda_condition
+ - private_swiftui_state
+
+file_header:
+ # Comments, except for the required and standard ones at the top of a Package.swift file
+ forbidden_pattern: //(?! (swift-tools-version:|The swift-tools-version declares the minimum version of Swift required to build this package\.))
+
+identifier_name:
+ &no_length_checks # We disable the length checks, for the same reason we disable the rules of type "metrics".
+ min_length:
+ warning: 1
+ max_length:
+ warning: 10000
+
+type_name: *no_length_checks
+
+generic_type_name: *no_length_checks
+
+# For compatibility with SwiftFormat
+trailing_comma:
+ mandatory_comma: true
diff --git a/.swiftpm/configuration/Package.resolved b/.swiftpm/configuration/Package.resolved
new file mode 100644
index 00000000..e955c270
--- /dev/null
+++ b/.swiftpm/configuration/Package.resolved
@@ -0,0 +1,42 @@
+{
+ "originHash" : "9d852a8f936d58dcf5f543508d205baeff66d9e230197f5987ee4f89aa7b1bf1",
+ "pins" : [
+ {
+ "identity" : "ably-cocoa",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/ably/ably-cocoa",
+ "state" : {
+ "revision" : "7f639c609e50053abd4590f34333f9472645558a",
+ "version" : "1.2.33"
+ }
+ },
+ {
+ "identity" : "delta-codec-cocoa",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/ably/delta-codec-cocoa",
+ "state" : {
+ "revision" : "3ee62ea40a63996b55818d44b3f0e56d8753be88",
+ "version" : "1.3.3"
+ }
+ },
+ {
+ "identity" : "msgpack-objective-c",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/rvi/msgpack-objective-C",
+ "state" : {
+ "revision" : "3e36b48e04ecd756cb927bd5f5b9bf6d45e475f9",
+ "version" : "0.4.0"
+ }
+ },
+ {
+ "identity" : "swift-argument-parser",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-argument-parser",
+ "state" : {
+ "revision" : "41982a3656a71c768319979febd796c6fd111d5c",
+ "version" : "1.5.0"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..54782e32
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/AblyChat.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/AblyChat.xcscheme
new file mode 100644
index 00000000..b1361f74
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/AblyChat.xcscheme
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AblyChat.xcworkspace/contents.xcworkspacedata b/AblyChat.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..56985fe1
--- /dev/null
+++ b/AblyChat.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/AblyChat.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/AblyChat.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/AblyChat.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 00000000..18c2c7de
--- /dev/null
+++ b/AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,42 @@
+{
+ "originHash" : "f6b591fa76437494c2609ca1208b8583cbc3debe5e659be06bdcb7bc4bb5e457",
+ "pins" : [
+ {
+ "identity" : "ably-cocoa",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/ably/ably-cocoa",
+ "state" : {
+ "revision" : "7f639c609e50053abd4590f34333f9472645558a",
+ "version" : "1.2.33"
+ }
+ },
+ {
+ "identity" : "delta-codec-cocoa",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/ably/delta-codec-cocoa",
+ "state" : {
+ "revision" : "3ee62ea40a63996b55818d44b3f0e56d8753be88",
+ "version" : "1.3.3"
+ }
+ },
+ {
+ "identity" : "msgpack-objective-c",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/rvi/msgpack-objective-C",
+ "state" : {
+ "revision" : "3e36b48e04ecd756cb927bd5f5b9bf6d45e475f9",
+ "version" : "0.4.0"
+ }
+ },
+ {
+ "identity" : "swift-argument-parser",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-argument-parser",
+ "state" : {
+ "revision" : "41982a3656a71c768319979febd796c6fd111d5c",
+ "version" : "1.5.0"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..a01c0764
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,62 @@
+# Contributing
+
+## Requirements
+
+- Mint TODO link then run `mint bootstrap`
+
+TODO
+
+- shellcheck
+- shfmt
+
+## Linting
+
+- To check code quality: `script/lint/check`
+- To fix code quality: `script/lint/fix`
+- To check formatting: `script/format/check`
+- To fix formatting: `script/format/fix`
+
+## How to build
+
+TODO
+
+## How we decide about versioning
+
+- We want to be able to compile using only one version of Xcode (i.e. the latest) so we won’t support platforms that generate deprecation warnings when we use them as a target for the latest version of Xcode
+
+At time of writing, the latest version of Xcode is 15.4, and the `Package.swift` `platforms` warnings say that the oldest supported versions are:
+
+- macOS 10.13
+- iOS 12.0
+- tvOS 12.0
+
+and then we also take the minimum of these and ably-cocoa (which at time of writing were all lower than the above)
+
+TODO let's see what Xcode 16 says too though — OK, doesn’t seem any different in Beta 3
+
+TODO we probably also have to take into account which simulators are available on the version of Xcode we're using, _and_ which are installed on the GitHub runner
+
+what are the oldest simulator runtimes that Xcode lets you install?
+
+In Xcode 15.4 (latest at time of writing), they are:
+
+- iOS 15.0
+- tvOS 15.0
+
+because, bear in mind that usually you can’t run the previous major version of Xcode on the next macOS, and usually there comes a point where the current major version of Xcode stops supporting the previous macOS
+
+TODO versioning will be fully determined in ECO-4893
+
+## Xcode versions
+
+TODO explain how we choose which Xcode version to test on
+
+## Example app versions
+
+this is a SwiftUI app and I want to use all the new Observable macros etc, so they will be in iOS 17 / macOS 14
+
+TODO document how to run example app on device
+
+TODO explain that all features should be demonstrable in the example app
+
+TODO explain the SwiftLint rule for generated comment headings
diff --git a/COPYRIGHT b/COPYRIGHT
new file mode 100644
index 00000000..625d86cf
--- /dev/null
+++ b/COPYRIGHT
@@ -0,0 +1 @@
+Copyright 2024 Ably Real-time Ltd (ably.com)
diff --git a/Example/AblyChatExample.xcodeproj/project.pbxproj b/Example/AblyChatExample.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..19bf03b1
--- /dev/null
+++ b/Example/AblyChatExample.xcodeproj/project.pbxproj
@@ -0,0 +1,386 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 56;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 21971DFF2C60D89C0074B8AE /* AblyChat in Frameworks */ = {isa = PBXBuildFile; productRef = 21971DFE2C60D89C0074B8AE /* AblyChat */; };
+ 21F09AA02C60CAF00025AF73 /* AblyChatExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F09A9F2C60CAF00025AF73 /* AblyChatExampleApp.swift */; };
+ 21F09AA22C60CAF00025AF73 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F09AA12C60CAF00025AF73 /* ContentView.swift */; };
+ 21F09AA42C60CAF20025AF73 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 21F09AA32C60CAF20025AF73 /* Assets.xcassets */; };
+ 21F09AA82C60CAF20025AF73 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 21F09AA72C60CAF20025AF73 /* Preview Assets.xcassets */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 21F09A9C2C60CAF00025AF73 /* AblyChatExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AblyChatExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 21F09A9F2C60CAF00025AF73 /* AblyChatExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AblyChatExampleApp.swift; sourceTree = ""; };
+ 21F09AA12C60CAF00025AF73 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+ 21F09AA32C60CAF20025AF73 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 21F09AA52C60CAF20025AF73 /* AblyChatExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AblyChatExample.entitlements; sourceTree = ""; };
+ 21F09AA72C60CAF20025AF73 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 21F09A992C60CAF00025AF73 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 21971DFF2C60D89C0074B8AE /* AblyChat in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 21971DFD2C60D89C0074B8AE /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 21F09A932C60CAF00025AF73 = {
+ isa = PBXGroup;
+ children = (
+ 21F09A9E2C60CAF00025AF73 /* AblyChatExample */,
+ 21F09A9D2C60CAF00025AF73 /* Products */,
+ 21971DFD2C60D89C0074B8AE /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 21F09A9D2C60CAF00025AF73 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 21F09A9C2C60CAF00025AF73 /* AblyChatExample.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 21F09A9E2C60CAF00025AF73 /* AblyChatExample */ = {
+ isa = PBXGroup;
+ children = (
+ 21F09A9F2C60CAF00025AF73 /* AblyChatExampleApp.swift */,
+ 21F09AA12C60CAF00025AF73 /* ContentView.swift */,
+ 21F09AA32C60CAF20025AF73 /* Assets.xcassets */,
+ 21F09AA52C60CAF20025AF73 /* AblyChatExample.entitlements */,
+ 21F09AA62C60CAF20025AF73 /* Preview Content */,
+ );
+ path = AblyChatExample;
+ sourceTree = "";
+ };
+ 21F09AA62C60CAF20025AF73 /* Preview Content */ = {
+ isa = PBXGroup;
+ children = (
+ 21F09AA72C60CAF20025AF73 /* Preview Assets.xcassets */,
+ );
+ path = "Preview Content";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 21F09A9B2C60CAF00025AF73 /* AblyChatExample */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 21F09AAB2C60CAF20025AF73 /* Build configuration list for PBXNativeTarget "AblyChatExample" */;
+ buildPhases = (
+ 21F09A982C60CAF00025AF73 /* Sources */,
+ 21F09A992C60CAF00025AF73 /* Frameworks */,
+ 21F09A9A2C60CAF00025AF73 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = AblyChatExample;
+ packageProductDependencies = (
+ 21971DFE2C60D89C0074B8AE /* AblyChat */,
+ );
+ productName = AblyChatExample;
+ productReference = 21F09A9C2C60CAF00025AF73 /* AblyChatExample.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 21F09A942C60CAF00025AF73 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1540;
+ LastUpgradeCheck = 1540;
+ TargetAttributes = {
+ 21F09A9B2C60CAF00025AF73 = {
+ CreatedOnToolsVersion = 15.4;
+ };
+ };
+ };
+ buildConfigurationList = 21F09A972C60CAF00025AF73 /* Build configuration list for PBXProject "AblyChatExample" */;
+ compatibilityVersion = "Xcode 14.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 21F09A932C60CAF00025AF73;
+ productRefGroup = 21F09A9D2C60CAF00025AF73 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 21F09A9B2C60CAF00025AF73 /* AblyChatExample */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 21F09A9A2C60CAF00025AF73 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 21F09AA82C60CAF20025AF73 /* Preview Assets.xcassets in Resources */,
+ 21F09AA42C60CAF20025AF73 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 21F09A982C60CAF00025AF73 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 21F09AA22C60CAF00025AF73 /* ContentView.swift in Sources */,
+ 21F09AA02C60CAF00025AF73 /* AblyChatExampleApp.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 21F09AA92C60CAF20025AF73 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_STRICT_CONCURRENCY = complete;
+ };
+ name = Debug;
+ };
+ 21F09AAA2C60CAF20025AF73 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_STRICT_CONCURRENCY = complete;
+ };
+ name = Release;
+ };
+ 21F09AAC2C60CAF20025AF73 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = AblyChatExample/AblyChatExample.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"AblyChatExample/Preview Content\"";
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
+ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 17.0;
+ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
+ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 14.0;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.ably.AblyChatExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = auto;
+ SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
+ SUPPORTS_MACCATALYST = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2,3";
+ TVOS_DEPLOYMENT_TARGET = 17.0;
+ };
+ name = Debug;
+ };
+ 21F09AAD2C60CAF20025AF73 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = AblyChatExample/AblyChatExample.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"AblyChatExample/Preview Content\"";
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
+ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 17.0;
+ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
+ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 14.0;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.ably.AblyChatExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = auto;
+ SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
+ SUPPORTS_MACCATALYST = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2,3";
+ TVOS_DEPLOYMENT_TARGET = 17.0;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 21F09A972C60CAF00025AF73 /* Build configuration list for PBXProject "AblyChatExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 21F09AA92C60CAF20025AF73 /* Debug */,
+ 21F09AAA2C60CAF20025AF73 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 21F09AAB2C60CAF20025AF73 /* Build configuration list for PBXNativeTarget "AblyChatExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 21F09AAC2C60CAF20025AF73 /* Debug */,
+ 21F09AAD2C60CAF20025AF73 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 21971DFE2C60D89C0074B8AE /* AblyChat */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = AblyChat;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = 21F09A942C60CAF00025AF73 /* Project object */;
+}
diff --git a/Example/AblyChatExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/AblyChatExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..919434a6
--- /dev/null
+++ b/Example/AblyChatExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Example/AblyChatExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/AblyChatExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/Example/AblyChatExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Example/AblyChatExample/AblyChatExample.entitlements b/Example/AblyChatExample/AblyChatExample.entitlements
new file mode 100644
index 00000000..f2ef3ae0
--- /dev/null
+++ b/Example/AblyChatExample/AblyChatExample.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.files.user-selected.read-only
+
+
+
diff --git a/Example/AblyChatExample/AblyChatExampleApp.swift b/Example/AblyChatExample/AblyChatExampleApp.swift
new file mode 100644
index 00000000..8f7fbab0
--- /dev/null
+++ b/Example/AblyChatExample/AblyChatExampleApp.swift
@@ -0,0 +1,11 @@
+import AblyChat
+import SwiftUI
+
+@main
+struct AblyChatExampleApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/Example/AblyChatExample/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/AblyChatExample/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 00000000..eb878970
--- /dev/null
+++ b/Example/AblyChatExample/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/AblyChatExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/AblyChatExample/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..532cd729
--- /dev/null
+++ b/Example/AblyChatExample/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,63 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/AblyChatExample/Assets.xcassets/Contents.json b/Example/AblyChatExample/Assets.xcassets/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/Example/AblyChatExample/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/AblyChatExample/ContentView.swift b/Example/AblyChatExample/ContentView.swift
new file mode 100644
index 00000000..6856f825
--- /dev/null
+++ b/Example/AblyChatExample/ContentView.swift
@@ -0,0 +1,21 @@
+import AblyChat
+import SwiftUI
+
+struct ContentView: View {
+ /// Just used to check that we can successfully import and use the AblyChat library. TODO remove this once we start building the library
+ @State private var ablyChatClient = AblyChatClient()
+
+ var body: some View {
+ VStack {
+ Image(systemName: "globe")
+ .imageScale(.large)
+ .foregroundStyle(.tint)
+ Text("Hello, world!")
+ }
+ .padding()
+ }
+}
+
+#Preview {
+ ContentView()
+}
diff --git a/Example/AblyChatExample/Preview Content/Preview Assets.xcassets/Contents.json b/Example/AblyChatExample/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/Example/AblyChatExample/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..d9a10c0d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,176 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
new file mode 100644
index 00000000..e5fda951
--- /dev/null
+++ b/MAINTAINERS.md
@@ -0,0 +1 @@
+This repository is owned by the Ably Ecosystems team.
diff --git a/Mintfile b/Mintfile
new file mode 100644
index 00000000..98ff1ee0
--- /dev/null
+++ b/Mintfile
@@ -0,0 +1,2 @@
+realm/SwiftLint@0.55.1
+nicklockwood/SwiftFormat@0.54.3
diff --git a/Package.resolved b/Package.resolved
new file mode 100644
index 00000000..18c2c7de
--- /dev/null
+++ b/Package.resolved
@@ -0,0 +1,42 @@
+{
+ "originHash" : "f6b591fa76437494c2609ca1208b8583cbc3debe5e659be06bdcb7bc4bb5e457",
+ "pins" : [
+ {
+ "identity" : "ably-cocoa",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/ably/ably-cocoa",
+ "state" : {
+ "revision" : "7f639c609e50053abd4590f34333f9472645558a",
+ "version" : "1.2.33"
+ }
+ },
+ {
+ "identity" : "delta-codec-cocoa",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/ably/delta-codec-cocoa",
+ "state" : {
+ "revision" : "3ee62ea40a63996b55818d44b3f0e56d8753be88",
+ "version" : "1.3.3"
+ }
+ },
+ {
+ "identity" : "msgpack-objective-c",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/rvi/msgpack-objective-C",
+ "state" : {
+ "revision" : "3e36b48e04ecd756cb927bd5f5b9bf6d45e475f9",
+ "version" : "0.4.0"
+ }
+ },
+ {
+ "identity" : "swift-argument-parser",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-argument-parser",
+ "state" : {
+ "revision" : "41982a3656a71c768319979febd796c6fd111d5c",
+ "version" : "1.5.0"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 00000000..0a5e76e3
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,66 @@
+// swift-tools-version: 5.10
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "AblyChat",
+ platforms: [
+ .macOS(.v10_13),
+ .iOS(.v12),
+ .tvOS(.v12),
+ ],
+ products: [
+ // Products define the executables and libraries a package produces, making them visible to other packages.
+ .library(
+ name: "AblyChat",
+ targets: [
+ "AblyChat",
+ ]
+ ),
+ ],
+ dependencies: [
+ .package(
+ url: "https://github.com/ably/ably-cocoa",
+ from: "1.2.0"
+ ),
+ .package(
+ url: "https://github.com/apple/swift-argument-parser",
+ from: "1.5.0"
+ ),
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package, defining a module or a test suite.
+ // Targets can depend on other targets in this package and products from dependencies.
+ .target(
+ name: "AblyChat",
+ dependencies: [
+ .product(
+ name: "Ably",
+ package: "ably-cocoa"
+ ),
+ ],
+ swiftSettings: [
+ .enableExperimentalFeature("StrictConcurrency"),
+ ]
+ ),
+ .testTarget(
+ name: "AblyChatTests",
+ dependencies: [
+ "AblyChat",
+ ]
+ ),
+ .executableTarget(
+ name: "BuildTool",
+ dependencies: [
+ .product(
+ name: "ArgumentParser",
+ package: "swift-argument-parser"
+ ),
+ ],
+ swiftSettings: [
+ .enableExperimentalFeature("StrictConcurrency"),
+ ]
+ ),
+ ]
+)
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..dcd34e03
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+# Ably Chat SDK for Swift
+
+TODO
+
+## Requirements
+
+- Swift 5.10 (that is, Xcode 15.3) or later (TODO choose what's right to use here)
diff --git a/Sources/AblyChat/AblyChat.swift b/Sources/AblyChat/AblyChat.swift
new file mode 100644
index 00000000..dc0816df
--- /dev/null
+++ b/Sources/AblyChat/AblyChat.swift
@@ -0,0 +1,7 @@
+import Ably
+
+/// Temporary class just used to check that the example app and tests can use the library. TODO remove this once we start building the library
+public class AblyChatClient {
+ /// Initializes an instance of `AblyChatClient`.
+ public init() {}
+}
diff --git a/Sources/BuildTool/BuildTool.swift b/Sources/BuildTool/BuildTool.swift
new file mode 100644
index 00000000..893ce72c
--- /dev/null
+++ b/Sources/BuildTool/BuildTool.swift
@@ -0,0 +1,236 @@
+import ArgumentParser
+import Foundation
+
+enum DestinationSpecifier {
+ case platform(String)
+ case deviceID(String)
+
+ var xcodebuildArgument: String {
+ switch self {
+ case let .platform(platform):
+ "platform=\(platform)"
+ case let .deviceID(deviceID):
+ "id=\(deviceID)"
+ }
+ }
+}
+
+enum DestinationStrategy {
+ case fixed(platform: String)
+ case lookup(destinationPredicate: DestinationPredicate)
+}
+
+struct DestinationPredicate {
+ // TODO: document
+ var runtime: String
+ var deviceType: String
+}
+
+enum Platform: String, CaseIterable {
+ case macOS
+ case iOS
+ case tvOS
+
+ var destinationStrategy: DestinationStrategy {
+ // TODO: why is xcodebuild giving locally with iOS "--- xcodebuild: WARNING: Using the first of multiple matching destinations:"
+ switch self {
+ case .macOS:
+ .fixed(platform: "macOS")
+ case .iOS:
+ .lookup(destinationPredicate: .init(runtime: "iOS-17-5", deviceType: "iPhone-15"))
+ case .tvOS:
+ .lookup(destinationPredicate: .init(runtime: "tvOS-17-5", deviceType: "Apple-TV-4K-3rd-generation-4K"))
+ }
+ }
+}
+
+extension Platform: ExpressibleByArgument {
+ init?(argument: String) {
+ self.init(rawValue: argument)
+ }
+}
+
+struct SimctlOutput: Codable {
+ var devices: [String: [Device]]
+
+ struct Device: Codable {
+ var udid: String
+ var deviceTypeIdentifier: String
+ }
+}
+
+enum Error: Swift.Error {
+ case terminatedWithExitCode(Int32)
+ case simulatorLookupFailed(message: String)
+}
+
+@main
+@available(macOS 14, *)
+struct BuildTool: ParsableCommand {
+ static let configuration = CommandConfiguration(
+ subcommands: [
+ BuildAndTestLibrary.self,
+ BuildExampleApp.self,
+ GenerateMatrices.self,
+ ]
+ )
+}
+
+// TODO: Is there a better way to make sure that this script has access to macOS APIs that are more recent than the package’s deployment target?
+@available(macOS 14, *)
+struct BuildAndTestLibrary: ParsableCommand {
+ @Option var platform: Platform
+ @Option var swiftVersion: Int
+
+ mutating func run() throws {
+ let destinationSpecifier: DestinationSpecifier = switch platform.destinationStrategy {
+ case let .fixed(platform):
+ .platform(platform)
+ case let .lookup(destinationPredicate):
+ try .deviceID(DestinationFetcher.fetchDeviceUDID(destinationPredicate: destinationPredicate))
+ }
+
+ let scheme = "AblyChat"
+
+ try XcodeRunner.runXcodebuild(action: nil, scheme: scheme, destination: destinationSpecifier, swiftVersion: swiftVersion)
+ try XcodeRunner.runXcodebuild(action: "test", scheme: scheme, destination: destinationSpecifier, swiftVersion: swiftVersion)
+ }
+}
+
+struct GenerateMatrices: ParsableCommand {
+ mutating func run() throws {
+ let matrix: [String: Any] = [
+ "tooling": [
+ [
+ "xcode-version": "15.3",
+ "swift-version": 5,
+ ],
+ [
+ "xcode-version": "16-beta",
+ "swift-version": 6,
+ ],
+ ],
+ "platform": Platform.allCases.map(\.rawValue),
+ ]
+
+ // I’m assuming the JSONSerialization output has no newlines
+ let keyValue = try "matrix=\(String(decoding: JSONSerialization.data(withJSONObject: matrix), as: UTF8.self))"
+ print(keyValue)
+ }
+}
+
+@available(macOS 14, *)
+struct BuildExampleApp: ParsableCommand {
+ @Option var platform: Platform
+ @Option var swiftVersion: Int
+
+ // TODO: DRY up
+ mutating func run() throws {
+ let destinationSpecifier: DestinationSpecifier = switch platform.destinationStrategy {
+ case let .fixed(platform):
+ .platform(platform)
+ case let .lookup(destinationPredicate):
+ try .deviceID(DestinationFetcher.fetchDeviceUDID(destinationPredicate: destinationPredicate))
+ }
+
+ try XcodeRunner.runXcodebuild(action: nil, scheme: "AblyChatExample", destination: destinationSpecifier, swiftVersion: swiftVersion)
+ }
+}
+
+@available(macOS 14, *)
+enum XcodeRunner {
+ static func runXcodebuild(action: String?, scheme: String, destination: DestinationSpecifier, swiftVersion: Int) throws {
+ var arguments: [String] = []
+
+ if let action {
+ arguments.append(action)
+ }
+
+ arguments.append(contentsOf: ["-scheme", scheme])
+ arguments.append(contentsOf: ["-destination", destination.xcodebuildArgument])
+
+ arguments.append(contentsOf: [
+ "SWIFT_TREAT_WARNINGS_AS_ERRORS=YES",
+ "SWIFT_VERSION=\(swiftVersion)",
+ ])
+
+ try ProcessRunner.run(executableName: "xcodebuild", arguments: arguments)
+ }
+}
+
+@available(macOS 14, *)
+enum DestinationFetcher {
+ static func fetchDeviceUDID(destinationPredicate: DestinationPredicate) throws -> String {
+ let simctlOutput = try fetchSimctlOutput()
+
+ let runtimeIdentifier = "com.apple.CoreSimulator.SimRuntime.\(destinationPredicate.runtime)"
+ let deviceTypeIdentifier = "com.apple.CoreSimulator.SimDeviceType.\(destinationPredicate.deviceType)"
+
+ let matchingDevices = (simctlOutput.devices[runtimeIdentifier] ?? []).filter { $0.deviceTypeIdentifier == deviceTypeIdentifier }
+
+ guard !matchingDevices.isEmpty else {
+ throw Error.simulatorLookupFailed(message: "Couldn’t find a simulator with runtime \(runtimeIdentifier) and device type \(deviceTypeIdentifier); available devices are \(simctlOutput.devices)")
+ }
+
+ guard matchingDevices.count == 1 else {
+ throw Error.simulatorLookupFailed(message: "Found multiple simulators with runtime \(runtimeIdentifier) and device type \(deviceTypeIdentifier); matching devices are \(matchingDevices)")
+ }
+
+ return matchingDevices[0].udid
+ }
+
+ private static func fetchSimctlOutput() throws -> SimctlOutput {
+ let data = try ProcessRunner.runAndReturnStdout(
+ executableName: "xcrun",
+ arguments: ["simctl", "list", "--json", "devices", "available"]
+ )
+
+ return try JSONDecoder().decode(SimctlOutput.self, from: data)
+ }
+}
+
+// I would have liked to use Swift concurrency for these but it felt like it would be a bit of a faff and it’s only a script. There’s a proposal for a Subprocess API coming up in Foundation which will marry Process with Swift concurrency.
+// TODO: Is there a better way to make sure that this script has access to macOS APIs that are more recent than the package’s deployment target?
+@available(macOS 14, *)
+enum ProcessRunner {
+ static func run(executableName: String, arguments: [String]) throws {
+ let process = Process()
+ process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
+ process.arguments = [executableName] + arguments
+
+ try process.run()
+ process.waitUntilExit()
+
+ if process.terminationStatus != 0 {
+ throw Error.terminatedWithExitCode(process.terminationStatus)
+ }
+ }
+
+ static func runAndReturnStdout(executableName: String, arguments: [String]) throws -> Data {
+ let process = Process()
+ process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
+ process.arguments = [executableName] + arguments
+
+ let standardOutput = Pipe()
+ process.standardOutput = standardOutput
+
+ try process.run()
+
+ var stdoutData = Data()
+ while true {
+ if let data = try standardOutput.fileHandleForReading.readToEnd() {
+ stdoutData.append(data)
+ } else {
+ break
+ }
+ }
+
+ process.waitUntilExit()
+
+ if process.terminationStatus != 0 {
+ throw Error.terminatedWithExitCode(process.terminationStatus)
+ }
+
+ return stdoutData
+ }
+}
diff --git a/Tests/AblyChatTests/AblyChatTests.swift b/Tests/AblyChatTests/AblyChatTests.swift
new file mode 100644
index 00000000..054ea7fe
--- /dev/null
+++ b/Tests/AblyChatTests/AblyChatTests.swift
@@ -0,0 +1,8 @@
+@testable import AblyChat
+import XCTest
+
+final class AblyChatTests: XCTestCase {
+ func testExample() throws {
+ XCTAssertNoThrow(AblyChatClient())
+ }
+}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..1240c3b6
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,30 @@
+{
+ "name": "ably-chat-swift-dev-tooling",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ably-chat-swift-dev-tooling",
+ "version": "0.1.0",
+ "devDependencies": {
+ "prettier": "^3.3.3"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
+ "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..c39c3a12
--- /dev/null
+++ b/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "ably-chat-swift-dev-tooling",
+ "version": "0.1.0",
+ "description": "Development tooling for the ably-chat-swift repo",
+ "devDependencies": {
+ "prettier": "^3.3.3"
+ },
+ "scripts": {
+ "format:check": "prettier --check .",
+ "format:fix": "prettier --write ."
+ }
+}
diff --git a/script/format/check b/script/format/check
new file mode 100755
index 00000000..2d9d1b71
--- /dev/null
+++ b/script/format/check
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+set -e
+
+# We use:
+#
+# - SwiftFormat for formatting Swift code
+# - Prettier for formatting everything else (e.g. YAML, JSON, Markdown)
+
+mint run swiftformat --lint .
+npm run format:check
diff --git a/script/format/fix b/script/format/fix
new file mode 100755
index 00000000..67370ee0
--- /dev/null
+++ b/script/format/fix
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+set -e
+
+mint run swiftformat .
+npm run format:fix
diff --git a/script/lint/check b/script/lint/check
new file mode 100755
index 00000000..3237b08f
--- /dev/null
+++ b/script/lint/check
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -e
+
+mint run swiftlint
diff --git a/script/lint/fix b/script/lint/fix
new file mode 100755
index 00000000..6b0d4733
--- /dev/null
+++ b/script/lint/fix
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -e
+
+mint run swiftlint --fix