Repository: launchdarkly/swift-eventsource
Branch: main
Commit: f63736db6fe7
Files: 56
Total size: 135.8 KB
Directory structure:
gitextract_1la2044u/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── actions/
│ │ ├── build-docs/
│ │ │ └── action.yml
│ │ ├── build-ios/
│ │ │ └── action.yml
│ │ ├── build-macos/
│ │ │ └── action.yml
│ │ ├── build-tvos/
│ │ │ └── action.yml
│ │ ├── build-watchos/
│ │ │ └── action.yml
│ │ ├── contract-tests/
│ │ │ └── action.yml
│ │ ├── lint/
│ │ │ └── action.yml
│ │ ├── publish/
│ │ │ └── action.yml
│ │ ├── publish-docs/
│ │ │ └── action.yml
│ │ ├── test-swiftpm/
│ │ │ └── action.yml
│ │ └── update-versions/
│ │ └── action.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── ci.yml
│ ├── lint-pr-title.yml
│ ├── manual-publish-docs.yml
│ ├── manual-publish.yml
│ ├── release-please.yml
│ └── stale.yml
├── .gitignore
├── .jazzy.yaml
├── .release-please-manifest.json
├── .swiftlint.yml
├── CHANGELOG.md
├── CODEOWNERS
├── CONTRIBUTING.md
├── ContractTestService/
│ ├── .gitignore
│ ├── Package.resolved
│ ├── Package.swift
│ ├── README.md
│ └── Sources/
│ └── ContractTestService/
│ └── main.swift
├── LDSwiftEventSource.podspec
├── LDSwiftEventSource.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ └── contents.xcworkspacedata
│ └── xcshareddata/
│ └── xcschemes/
│ └── LDSwiftEventSource.xcscheme
├── LICENSE.txt
├── Makefile
├── Package.swift
├── README.md
├── SECURITY.md
├── Source/
│ ├── .swiftlint.yml
│ ├── EventParser.swift
│ ├── Info.plist
│ ├── LDSwiftEventSource.h
│ ├── LDSwiftEventSource.swift
│ ├── Logs.swift
│ ├── Types.swift
│ └── UTF8LineParser.swift
├── Tests/
│ ├── .swiftlint.yml
│ ├── EventParserTests.swift
│ ├── LDSwiftEventSourceTests.swift
│ ├── MockHandler.swift
│ ├── TestUtil.swift
│ └── UTF8LineParserTests.swift
└── release-please-config.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To reproduce**
Steps to reproduce the behavior.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Logs**
If applicable, add any log output related to your problem.
**Library version**
The version that you are using.
**XCode and Swift version**
For instance, XCode 11.5, Swift 5.1.
**Platform the issue occurs on**
iPhone, iPad, macOS, tvOS, or watchOS.
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I would love to see the library [...does something new...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context about the feature request here.
================================================
FILE: .github/actions/build-docs/action.yml
================================================
name: Build Documentation
description: 'Build Documentation.'
runs:
using: composite
steps:
- name: Install jazzy gem
shell: bash
run: gem install jazzy
- name: Build Documentation
shell: bash
run: jazzy -o docs
- name: Validate coverage
shell: bash
run: |
FULLDOC=`jq '.warnings | length == 0' docs/undocumented.json`
[ $FULLDOC == "true" ]
================================================
FILE: .github/actions/build-ios/action.yml
================================================
name: Build & Test iOS
description: 'Build for iOS device and run tests on iOS Simulator.'
inputs:
ios-sim:
description: 'iOS Simulator to use for testing'
required: true
runs:
using: composite
steps:
# Workaround for intermittent macos-15 runner issue where simulator
# runtimes aren't loaded. Listing devices forces the simulator service
# to initialize. See https://github.com/actions/runner-images/issues/12948
- name: Prepare iOS Simulator runtime
shell: bash
run: xcrun simctl list devices available
- name: Build Tests for iOS device
shell: bash
run: xcodebuild build-for-testing -scheme 'LDSwiftEventSource' -sdk iphoneos CODE_SIGN_IDENTITY= | xcpretty
- name: Build & Test on iOS Simulator
shell: bash
run: xcodebuild test -scheme 'LDSwiftEventSource' -sdk iphonesimulator -destination '${{ inputs.ios-sim }}' CODE_SIGN_IDENTITY= | xcpretty
================================================
FILE: .github/actions/build-macos/action.yml
================================================
name: Build & Test macOS
description: 'Build and test for macOS.'
runs:
using: composite
steps:
- name: Build & Test on macOS
shell: bash
run: xcodebuild test -scheme 'LDSwiftEventSource' -sdk macosx -destination 'platform=macOS' | xcpretty
- name: Build for ARM64 macOS
shell: bash
run: xcodebuild build -scheme 'LDSwiftEventSource' -arch arm64e -sdk macosx | xcpretty
================================================
FILE: .github/actions/build-tvos/action.yml
================================================
name: Build & Test tvOS
description: 'Build for tvOS device and run tests on tvOS Simulator.'
runs:
using: composite
steps:
# Workaround for intermittent macos-15 runner issue where simulator
# runtimes aren't loaded. Listing devices forces the simulator service
# to initialize. See https://github.com/actions/runner-images/issues/12948
- name: Prepare tvOS Simulator runtime
shell: bash
run: xcrun simctl list devices available
- name: Build Tests for tvOS device
shell: bash
run: xcodebuild build-for-testing -scheme 'LDSwiftEventSource' -sdk appletvos CODE_SIGN_IDENTITY= | xcpretty
- name: Build & Test on tvOS Simulator
shell: bash
run: xcodebuild test -scheme 'LDSwiftEventSource' -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV' | xcpretty
================================================
FILE: .github/actions/build-watchos/action.yml
================================================
name: Build watchOS
description: 'Build for watchOS device and simulator.'
runs:
using: composite
steps:
# Workaround for intermittent macos-15 runner issue where simulator
# runtimes aren't loaded. Listing devices forces the simulator service
# to initialize. See https://github.com/actions/runner-images/issues/12948
- name: Prepare watchOS Simulator runtime
shell: bash
run: xcrun simctl list devices available
- name: Build for watchOS simulator
shell: bash
run: xcodebuild build -scheme 'LDSwiftEventSource' -sdk watchsimulator | xcpretty
- name: Build for watchOS device
shell: bash
run: xcodebuild build -scheme 'LDSwiftEventSource' -sdk watchos | xcpretty
================================================
FILE: .github/actions/contract-tests/action.yml
================================================
name: Contract Tests
description: 'Build and run SDK contract tests.'
inputs:
token:
description: 'GH token used to download SDK test harness.'
required: true
runs:
using: composite
steps:
- name: Build contract test service
shell: bash
run: make build-contract-tests
- name: Start contract test service
shell: bash
run: make start-contract-test-service-bg
- name: Run contract tests
uses: launchdarkly/gh-actions/actions/contract-tests@main
with:
repo: sse-contract-tests
branch: main
version: v2
token: ${{ inputs.token }}
test_service_port: '8000'
debug_logging: 'true'
enable_persistence_tests: 'false'
extra_params: "-skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks'"
================================================
FILE: .github/actions/lint/action.yml
================================================
name: Lint
description: 'Run podspec and swiftlint checks.'
runs:
using: composite
steps:
- name: Install swiftlint
shell: bash
run: brew install swiftlint
- name: Install cocoapods
shell: bash
run: gem install cocoapods
- name: Lint the podspec
shell: bash
# --quick skips building since dedicated build jobs already compile all platforms
run: pod lib lint LDSwiftEventSource.podspec --allow-warnings --quick
- name: Run swiftlint
shell: bash
run: swiftlint lint
================================================
FILE: .github/actions/publish/action.yml
================================================
name: Publish Package
description: 'Publish the package to Cocoapods'
inputs:
dry_run:
description: 'Is this a dry run. If so no package will be published.'
required: true
runs:
using: composite
steps:
- name: Push to cocoapods
if: ${{ inputs.dry_run == 'false' }}
shell: bash
run: pod trunk push LDSwiftEventSource.podspec --allow-warnings --verbose
================================================
FILE: .github/actions/publish-docs/action.yml
================================================
name: Publish Documentation
description: 'Publish the documentation to GitHub pages'
inputs:
token:
description: 'Token to use for publishing.'
required: true
runs:
using: composite
steps:
- uses: launchdarkly/gh-actions/actions/publish-pages@publish-pages-v1.0.2
name: 'Publish to GitHub pages'
with:
docs_path: docs
github_token: ${{ inputs.token }}
================================================
FILE: .github/actions/test-swiftpm/action.yml
================================================
name: Test SwiftPM
description: 'Build and test using Swift Package Manager.'
runs:
using: composite
steps:
- name: Build & Test with SwiftPM
shell: bash
run: swift test -v 2>&1 | xcpretty
================================================
FILE: .github/actions/update-versions/action.yml
================================================
name: Update xcode project version numbers
description: 'Update xcode project version numbers'
inputs:
branch:
description: 'The branch to checkout and push updates to'
required: true
runs:
using: composite
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.branch }}
- name: Calculate version numbers
id: version
shell: bash
run: |
version=$(jq -r '."."' .release-please-manifest.json)
major=$(echo "$version" | cut -f1 -d.)
minor=$(echo "$version" | cut -f2 -d.)
patch=$(echo "$version" | cut -f3 -d.)
# 64 + version gives us a letter offset for the framework version.
framework=$(echo $((major + 64)) | awk '{ printf("%c", $1) }')
echo "major=${major}" >> "$GITHUB_OUTPUT"
echo "minor=${minor}" >> "$GITHUB_OUTPUT"
echo "patch=${patch}" >> "$GITHUB_OUTPUT"
echo "framework=${framework}" >> "$GITHUB_OUTPUT"
- name: Update other version numbers
shell: bash
run: |
sed -i .bak -E \
-e 's/MARKETING_VERSION = [^;]+/MARKETING_VERSION = ${{ steps.version.outputs.major }}.${{ steps.version.outputs.minor }}.${{ steps.version.outputs.patch }}/' \
-e 's/DYLIB_CURRENT_VERSION = [^;]+/DYLIB_CURRENT_VERSION = ${{ steps.version.outputs.major }}.${{ steps.version.outputs.minor }}.${{ steps.version.outputs.patch }}/' \
-e 's/DYLIB_COMPATIBILITY_VERSION = [^;]+/DYLIB_COMPATIBILITY_VERSION = ${{ steps.version.outputs.major }}.0.0/' \
-e 's/FRAMEWORK_VERSION = .*/FRAMEWORK_VERSION = ${{ steps.version.outputs.framework }};/' \
LDSwiftEventSource.xcodeproj/project.pbxproj
sed -i .bak -E \
-e "s/pod 'LDSwiftEventSource', '~> [0-9]+.[0-9]+'/pod 'LDSwiftEventSource', '~> ${{ steps.version.outputs.major }}.${{ steps.version.outputs.minor }}'/" \
-e "s/github \"LaunchDarkly\/swift-eventsource\" ~> [0-9]+.[0-9]+/github \"LaunchDarkly\/swift-eventsource\" ~> ${{ steps.version.outputs.major }}.${{ steps.version.outputs.minor }}/" README.md
rm -f LDSwiftEventSource.xcodeproj/project.pbxproj.bak README.md.bak
if [ $(git status --porcelain | wc -l) -gt 0 ]; then
git config --global user.name 'LaunchDarklyReleaseBot'
git config --global user.email 'LaunchDarklyReleaseBot@launchdarkly.com'
git add LDSwiftEventSource.xcodeproj/project.pbxproj
git add README.md
git commit -m 'Updating generated project and readme files'
git push
fi
================================================
FILE: .github/pull_request_template.md
================================================
**Requirements**
- [ ] I have added test coverage for new or changed functionality
- [ ] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests)
- [ ] I have validated my changes against all supported platform versions
**Related issues**
Provide links to any issues in this repository or elsewhere relating to this pull request.
**Describe the solution you've provided**
Provide a clear and concise description of what you expect to happen.
**Describe alternatives you've considered**
Provide a clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context about the pull request here.
================================================
FILE: .github/workflows/ci.yml
================================================
name: Run CI
on:
push:
branches: [ main ]
paths-ignore:
- '**.md' # Do not need to run CI for markdown changes.
pull_request:
branches: [ main ]
paths-ignore:
- '**.md'
jobs:
lint:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
with:
xcode-version: 16.4
- uses: ./.github/actions/lint
build-ios:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
with:
xcode-version: 16.4
- uses: ./.github/actions/build-ios
with:
ios-sim: 'platform=iOS Simulator,name=iPhone 16'
build-macos:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
with:
xcode-version: 16.4
- uses: ./.github/actions/build-macos
build-tvos:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
with:
xcode-version: 16.4
- uses: ./.github/actions/build-tvos
build-watchos:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
with:
xcode-version: 16.4
- uses: ./.github/actions/build-watchos
test-swiftpm:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
with:
xcode-version: 16.4
- uses: ./.github/actions/test-swiftpm
contract-tests:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
with:
xcode-version: 16.4
- uses: ./.github/actions/contract-tests
with:
token: ${{ secrets.GITHUB_TOKEN }}
build-docs:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
with:
xcode-version: 16.4
- uses: ./.github/actions/build-docs
linux-build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
swift-version:
- 5.7
- 5.8
- 5.9
container: swift:${{ matrix.swift-version }}
steps:
- uses: actions/checkout@v4
- name: Build and test
run: swift test --enable-test-discovery
windows-build:
name: Windows - Swift 6.1
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install Swift
uses: compnerd/gha-setup-swift@cd348eb89f2f450b0664c07fb1cb66880addf17d
with:
branch: swift-6.1-release
tag: 6.1-RELEASE
- name: Build and test
run: swift test
================================================
FILE: .github/workflows/lint-pr-title.yml
================================================
name: Lint PR title
on:
pull_request_target:
types:
- opened
- edited
- synchronize
jobs:
lint-pr-title:
uses: launchdarkly/gh-actions/.github/workflows/lint-pr-title.yml@main
================================================
FILE: .github/workflows/manual-publish-docs.yml
================================================
on:
workflow_dispatch:
name: Publish Documentation
jobs:
build-publish:
runs-on: macos-15
permissions:
id-token: write # Needed if using OIDC to get release secrets.
contents: write # Needed in this case to write github pages.
steps:
- uses: actions/checkout@v4
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
with:
xcode-version: 16.4
- uses: ./.github/actions/lint
- uses: ./.github/actions/build-ios
with:
ios-sim: 'platform=iOS Simulator,name=iPhone 16'
- uses: ./.github/actions/build-macos
- uses: ./.github/actions/build-tvos
- uses: ./.github/actions/build-watchos
- uses: ./.github/actions/test-swiftpm
- uses: ./.github/actions/contract-tests
with:
token: ${{ secrets.GITHUB_TOKEN }}
- uses: ./.github/actions/build-docs
- uses: ./.github/actions/publish-docs
with:
token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/manual-publish.yml
================================================
name: Publish Package
on:
workflow_dispatch:
inputs:
dry_run:
description: 'Is this a dry run. If so no package will be published.'
type: boolean
required: true
jobs:
build-publish:
runs-on: macos-15
# Needed to get tokens during publishing.
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0
name: 'Get Cocoapods token'
with:
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
ssm_parameter_pairs: '/production/common/releasing/cocoapods/token = COCOAPODS_TRUNK_TOKEN'
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
with:
xcode-version: 16.4
- uses: ./.github/actions/lint
- uses: ./.github/actions/build-ios
with:
ios-sim: 'platform=iOS Simulator,name=iPhone 16'
- uses: ./.github/actions/build-macos
- uses: ./.github/actions/build-tvos
- uses: ./.github/actions/build-watchos
- uses: ./.github/actions/test-swiftpm
- uses: ./.github/actions/contract-tests
with:
token: ${{ secrets.GITHUB_TOKEN }}
- uses: ./.github/actions/publish
with:
dry_run: ${{ inputs.dry_run }}
================================================
FILE: .github/workflows/release-please.yml
================================================
name: Run Release Please
on:
push:
branches:
- main
jobs:
release-package:
runs-on: macos-15
permissions:
id-token: write # Needed if using OIDC to get release secrets.
contents: write # Contents and pull-requests are for release-please to make releases.
pull-requests: write
steps:
- uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0
id: release
with:
target-branch: ${{ github.ref_name }}
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history is required for proper changelog generation
#
# This step runs and updates an existing PR
#
- uses: ./.github/actions/update-versions
if: ${{ steps.release.outputs.prs_created == 'true' }}
with:
branch: ${{ fromJSON(steps.release.outputs.pr).headBranchName }}
#
# These remaining steps are ONLY run if a release was actually created
#
- uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0
if: ${{ steps.release.outputs.releases_created == 'true' }}
name: 'Get Cocoapods token'
with:
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
ssm_parameter_pairs: '/production/common/releasing/cocoapods/token = COCOAPODS_TRUNK_TOKEN'
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # 60606e260d2fc5762a71e64e74b2174e8ea3c8bd
if: ${{ steps.release.outputs.releases_created == 'true' }}
with:
xcode-version: 16.4
- uses: ./.github/actions/lint
if: ${{ steps.release.outputs.releases_created == 'true' }}
- uses: ./.github/actions/build-ios
if: ${{ steps.release.outputs.releases_created == 'true' }}
with:
ios-sim: 'platform=iOS Simulator,name=iPhone 16'
- uses: ./.github/actions/build-macos
if: ${{ steps.release.outputs.releases_created == 'true' }}
- uses: ./.github/actions/build-tvos
if: ${{ steps.release.outputs.releases_created == 'true' }}
- uses: ./.github/actions/build-watchos
if: ${{ steps.release.outputs.releases_created == 'true' }}
- uses: ./.github/actions/test-swiftpm
if: ${{ steps.release.outputs.releases_created == 'true' }}
- uses: ./.github/actions/contract-tests
if: ${{ steps.release.outputs.releases_created == 'true' }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
- uses: ./.github/actions/build-docs
if: ${{ steps.release.outputs.releases_created == 'true' }}
- uses: ./.github/actions/publish
if: ${{ steps.release.outputs.releases_created == 'true' }}
with:
token: ${{secrets.GITHUB_TOKEN}}
dry_run: false
- uses: ./.github/actions/publish-docs
if: ${{ steps.release.outputs.releases_created == 'true' }}
with:
token: ${{secrets.GITHUB_TOKEN}}
================================================
FILE: .github/workflows/stale.yml
================================================
name: "Close stale issues and PRs"
on:
workflow_dispatch:
schedule:
# Happen once per day at 1:30 AM
- cron: "30 1 * * *"
permissions:
issues: write
pull-requests: write
jobs:
sdk-close-stale:
uses: launchdarkly/gh-actions/.github/workflows/sdk-stale.yml@main
================================================
FILE: .gitignore
================================================
*~
\#*
.\#*
.DS_Store
/.build
xcuserdata/
IDEWorkspaceChecks.plist
.swiftpm
/docs
================================================
FILE: .jazzy.yaml
================================================
module: LDSwiftEventSource
author: LaunchDarkly
author_url: https://launchdarkly.com
github_url: https://github.com/launchdarkly/swift-eventsource
clean: true
swift_build_tool: spm
readme: README.md
documentation:
- CHANGELOG.md
- CONTRIBUTING.md
- LICENSE.txt
copyright: 'Copyright © 2020 Catamorphic Co.'
================================================
FILE: .release-please-manifest.json
================================================
{
".": "3.3.0"
}
================================================
FILE: .swiftlint.yml
================================================
# See sub-configurations at `Source/.swiftlint.yml` and `Tests/.swiftlint.yml`.
disabled_rules:
- identifier_name
- weak_delegate
opt_in_rules:
- anyobject_protocol
- array_init
- attributes
- closure_body_length
- closure_end_indentation
- closure_spacing
- collection_alignment
- conditional_returns_on_newline
- contains_over_filter_count
- contains_over_filter_is_empty
- contains_over_first_not_nil
- contains_over_range_nil_comparison
- discouraged_object_literal
- discouraged_optional_boolean
- discouraged_optional_collection
- empty_collection_literal
- empty_count
- empty_string
- empty_xctest_method
- enum_case_associated_values_count
- expiring_todo
- explicit_init
- explicit_self
- extension_access_modifier
- fallthrough
- fatal_error_message
- file_header
- file_name_no_space
- first_where
- flatmap_over_map_reduce
- function_default_parameter_at_end
- identical_operands
- implicit_return
- joined_default_parameter
- last_where
- legacy_multiple
- legacy_random
- let_var_whitespace
- literal_expression_end_indentation
- missing_docs
- modifier_order
- no_grouping_extension
- nslocalizedstring_key
- nslocalizedstring_require_bundle
- number_separator
- object_literal
- operator_usage_whitespace
- optional_enum_case_matching
- overridden_super_call
- override_in_extension
- pattern_matching_keywords
- prefer_self_type_over_type_of_self
- prefixed_toplevel_constant
- private_action
- private_outlet
- prohibited_interface_builder
- prohibited_super_call
- raw_value_for_camel_cased_codable_enum
- reduce_into
- redundant_nil_coalescing
- required_enum_case
- single_test_class
- sorted_first_last
- static_operator
- strict_fileprivate
- strong_iboutlet
- switch_case_on_newline
- toggle_bool
- trailing_closure
- unavailable_function
- unneeded_parentheses_in_closure_argument
- unowned_variable_capture
- untyped_error_in_catch
- unused_declaration
- unused_import
- vertical_parameter_alignment_on_call
- vertical_whitespace_closing_braces
- vertical_whitespace_opening_braces
- yoda_condition
included:
- Source
- Tests
reporter: "xcode"
================================================
FILE: CHANGELOG.md
================================================
# Change log
All notable changes to the LaunchDarkly Swift EventSource library will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
## [3.3.0](https://github.com/launchdarkly/swift-eventsource/compare/3.2.0...3.3.0) (2024-05-31)
### Features
* adds ability to provide a OSLog instance via the Config.logger property ([c10ec29](https://github.com/launchdarkly/swift-eventsource/commit/c10ec2936e77959f828a041b71ea56e454e39ff2))
* adds ability to provide a OSLog instance via the Config.logger property ([#78](https://github.com/launchdarkly/swift-eventsource/issues/78)) ([2220929](https://github.com/launchdarkly/swift-eventsource/commit/2220929cbc98edd88e0e7d8b5ccb1f3992b4cf4f))
## [3.2.0](https://github.com/launchdarkly/swift-eventsource/compare/3.1.1...3.2.0) (2023-12-29)
### Features
* Add Compilation & Testing On Windows ([#68](https://github.com/launchdarkly/swift-eventsource/issues/68)) ([ac5f18c](https://github.com/launchdarkly/swift-eventsource/commit/ac5f18ccb5b197bbc9f37f9f799017a397eee43e))
## [3.1.1] - 2023-06-12
### Fixed:
- Per the SSE spec, an HTTP 204 will now halt retry attempts by default.
## [3.1.0] - 2023-06-05
### Changed:
- Enforce TLS v1.2 as a required minimum.
### Fixed:
- Fix re-entrancy issue with `start` command. (Thanks, [g-mark](https://github.com/launchdarkly/swift-eventsource/pull/56)!)
## [3.0.0] - 2022-10-06
### Changed
- Dropped support for older versions in accordance with the new [Xcode 14 release](https://developer.apple.com/documentation/xcode-release-notes/xcode-14-release-notes).
## [2.0.0] - 2022-08-29
### Changed
- The CI build now incorporates the cross-platform contract tests defined in https://github.com/launchdarkly/sse-contract-tests to ensure consistent test coverage across different LaunchDarkly SSE implementations.
- Removed explicit typed package products. Thanks to @simba909 for the PR ([#48](https://github.com/launchdarkly/swift-eventsource/pull/48)).
## [1.3.1] - 2022-03-11
### Fixed
- Fixed a race condition that could cause a crash when `stop()` is called when there is a pending reconnection attempt.
## [1.3.0] - 2022-01-18
### Added
- Added the configuration option `urlSessionConfiguration` to `EventSource.Config` which allows setting the `URLSessionConfiguration` used by the `EventSource` to create `URLSession` instances.
### Fixed
- Fixed a retain cycle issue when the stream connection is ended.
- Removed deprecated `VALID_ARCHS` build setting from Xcode project.
- Unterminated events will no longer be dispatched when the stream connection is dropped.
- Stream events that set the `lastEventId` will now record the updated `lastEventId` even if the event does not generate a `MessageEvent`.
- Empty stream "data" fields will now always record a newline to the resultant `MessageEvent` data.
- Empty stream "event" fields will result in now result in the default "message" event type rather than an event type of "".
## [1.2.1] - 2021-02-10
### Added
- [SwiftLint](https://github.com/realm/SwiftLint) configuration. Linting will be automatically run as part of the build if [Mint](https://github.com/yonaskolb/Mint) is installed.
- Support for building docs with [jazzy](https://github.com/realm/jazzy). These docs are available through [GitHub Pages](https://launchdarkly.github.io/swift-eventsource/).
### Fixed
- Reconnection backoff was always reset if the previous successful connection was at least `backoffResetThreshold` prior to the scheduling of a reconnection attempt. The connection backoff has been corrected to not reset after the first reconnection attempt until the next successful connection. Thanks to @tomasf for the PR ([#14](https://github.com/launchdarkly/swift-eventsource/pull/14)).
- On an `UnsuccessfulResponseError` the configured `connectionErrorHandler` would be called twice, the second time with a `URLError.cancelled` error. Only if the second call returned `ConnectionErrorAction.shutdown` would the `EventSource` client actually shutdown. This has been corrected to only call the `connectionErrorHandler` once, and will shutdown the client if `ConnectionErrorAction.shutdown` is returned. Thanks to @tomasf for the PR ([#13](https://github.com/launchdarkly/swift-eventsource/pull/13)).
- A race condition that could cause the `EventSource` client to restart after shutting down has been fixed.
## [1.2.0] - 2020-10-21
### Added
- Added `headerTransform` closure to `LDConfig` to allow dynamic http header configuration.
## [1.1.0] - 2020-07-20
### Added
- Support `arm64e` on `appletvos`, `iphoneos`, and `macosx` SDKs by extending valid architectures.
- Support for building LDSwiftEventSource on Linux. Currently this library will not generate log messages on Linux, and may not behave correctly on Linux due to Foundation being [incomplete](https://github.com/apple/swift-corelibs-foundation/blob/main/Docs/Status.md).
## [1.0.0] - 2020-07-16
This is the first public release of the LDSwiftEventSource library. The following notes are what changed since the previous pre-release version.
### Changed
- Renamed `EventHandler.onMessage` parameter `event` to `eventType`.
- The `EventSource` class no longer extends `NSObject` or `URLSessionDataDelegate` to not expose `urlSession` functions.
## [0.5.0] - 2020-07-14
### Changed
- Default `LDSwiftEventSource` product defined for the SwiftPM package is now explicitly a dynamic product. An explicitly static product is now available as `LDSwiftEventSourceStatic`.
## [0.4.0] - 2020-07-13
### Changed
- Converted build system to use a single target to produce a universal framework, rather than separate targets for each platform that share a product name. This is to prevent issues with `xcodebuild` resolving the build scheme to an incorrect platform when building dependent packages with 'Find Implicit Dependencies' enabled. This is due to a bug in `xcodebuild`, for more information see [http://www.openradar.me/20490378](http://www.openradar.me/20490378) and [http://www.openradar.me/22008701](http://www.openradar.me/22008701).
## [0.3.0] - 2020-06-02
### Added
- Added `stop()` method to shutdown the EventSource connection.
### Changed
- Logging `subsystem` renamed from `com.launchdarkly.swift-event-source` to `com.launchdarkly.swift-eventsource`
## [0.2.0] - 2020-05-21
### Added
- Public constructors for `UnsuccessfulResponseError` and `MessageEvent` to allow consumers of the library to use them for unit tests.
## [0.1.0] - 2020-05-09
### Added
- Initial implementation for internal alpha testing.
================================================
FILE: CODEOWNERS
================================================
# Repository Maintainers
* @launchdarkly/team-sdk-swift
================================================
FILE: CONTRIBUTING.md
================================================
Contributing to the LDSwiftEventSource library
================================================
Submitting bug reports and feature requests
------------------
The LaunchDarkly SDK team monitors the [issue tracker](https://github.com/launchdarkly/swift-eventsource/issues) for the EventSource repository. Bug reports and feature requests specific to this library should be filed in this issue tracker.
Submitting pull requests
------------------
We encourage pull requests and other contributions from the community. Before submitting pull requests, ensure that all temporary or unintended code is removed. Don't worry about adding reviewers to the pull request; the LaunchDarkly SDK team will add themselves.
Build instructions
------------------
### Prerequisites
This library is built with [XCode](https://developer.apple.com/xcode/) or [SwiftPM](https://swift.org/package-manager/). The [CI build](https://github.com/launchdarkly/swift-eventsource/actions/workflows/ci.yml) builds and tests various configurations of the library on various systems, platforms, and devices. For details, see [the GitHub action CI configuration][ci-config].
### Building And Testing
This library can be built directly with the Swift package manager, or through XCode. To build and run tests using SwiftPM simply:
```bash
swift test
```
Or in XCode, simply select the desired target and select `Product -> Test`.
For building on the command line with `xcodebuild`, see the [continuous integration build configuration][ci-config] for examples on building and running tests.
### Running contract tests
To run the standardized contract tests that are run against all LaunchDarkly SSE client implementations:
```
make contract-tests
```
### Generating API documentation
Docs are built with [jazzy](https://github.com/realm/jazzy), which is configured [here](https://github.com/launchdarkly/swift-eventsource/blob/main/.jazzy.yaml). To build them, simply run `jazzy`. Pull requests should keep our documentation coverage at 100%.
[ci-config]: https://github.com/launchdarkly/swift-eventsource/blob/main/.github/workflows/ci.yml
================================================
FILE: ContractTestService/.gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
================================================
FILE: ContractTestService/Package.resolved
================================================
{
"object": {
"pins": [
{
"package": "Socket",
"repositoryURL": "https://github.com/Kitura/BlueSocket.git",
"state": {
"branch": null,
"revision": "c9894fd117457f1d006575fbfb2fdfd6f79eac03",
"version": "1.0.200"
}
},
{
"package": "SSLService",
"repositoryURL": "https://github.com/Kitura/BlueSSLService.git",
"state": {
"branch": null,
"revision": "ae5889d2a8b068d2d3ab91ec73aa8f557c03cd8a",
"version": "1.0.200"
}
},
{
"package": "Kitura",
"repositoryURL": "https://github.com/Kitura/Kitura",
"state": {
"branch": null,
"revision": "daf6479097469a0c1fe64755db6e23f788a3ec29",
"version": "2.9.200"
}
},
{
"package": "Kitura-net",
"repositoryURL": "https://github.com/Kitura/Kitura-net.git",
"state": {
"branch": null,
"revision": "e017a57772eb477c294d72b25ab9ae38d25539f5",
"version": "2.4.200"
}
},
{
"package": "Kitura-TemplateEngine",
"repositoryURL": "https://github.com/Kitura/Kitura-TemplateEngine.git",
"state": {
"branch": null,
"revision": "1670b09bfb63b66f7dffff627a06b50492485407",
"version": "2.0.200"
}
},
{
"package": "KituraContracts",
"repositoryURL": "https://github.com/Kitura/KituraContracts.git",
"state": {
"branch": null,
"revision": "8a4778c3aa7833e9e1af884e8819d436c237cd70",
"version": "1.2.201"
}
},
{
"package": "LoggerAPI",
"repositoryURL": "https://github.com/Kitura/LoggerAPI.git",
"state": {
"branch": null,
"revision": "e82d34eab3f0b05391082b11ea07d3b70d2f65bb",
"version": "1.9.200"
}
},
{
"package": "swift-log",
"repositoryURL": "https://github.com/apple/swift-log.git",
"state": {
"branch": null,
"revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
"version": "1.4.2"
}
},
{
"package": "TypeDecoder",
"repositoryURL": "https://github.com/Kitura/TypeDecoder.git",
"state": {
"branch": null,
"revision": "28ec01815c0aea9236f92982ca8d351e7112a4a0",
"version": "1.3.201"
}
}
]
},
"version": 1
}
================================================
FILE: ContractTestService/Package.swift
================================================
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "ContractTestService",
platforms: [
.iOS(.v11),
.macOS(.v10_13),
.watchOS(.v4),
.tvOS(.v11),
],
products: [
.executable(
name: "contract-test-service",
targets: ["ContractTestService"]
)
],
dependencies: [
// Local dependency to LDSwiftEventSource
.package(path: ".."),
.package(url: "https://github.com/Kitura/Kitura", from: "2.9.200")
],
targets: [
.target(
name: "ContractTestService",
dependencies: [
"LDSwiftEventSource",
"Kitura"
]
)
]
)
================================================
FILE: ContractTestService/README.md
================================================
# SSE client contract test service
This directory contains an implementation of the cross-platform SSE testing protocol defined by https://github.com/launchdarkly/sse-contract-tests. See that project's `README` for details of this protocol, and the kinds of SSE client capabilities that are relevant to the contract tests. This code should not need to be updated unless the SSE client has added or removed such capabilities.
To run these tests locally, run `make contract-tests` from the project root directory. This downloads the correct version of the test harness tool automatically.
================================================
FILE: ContractTestService/Sources/ContractTestService/main.swift
================================================
import Dispatch
import Foundation
import Kitura
import LDSwiftEventSource
struct StatusResp: Encodable {
let name = "swift-eventsource"
let capabilities = ["server-directed-shutdown-request", "comments", "headers", "last-event-id", "post", "read-timeout", "report"]
}
struct CreateStreamReq: Decodable {
let streamUrl: URL
let callbackUrl: URL
let initialDelayMs: Int?
let readTimeoutMs: Int?
let lastEventId: String?
let headers: [String: String]?
let method: String?
let body: String?
func createEventSourceConfig() -> EventSource.Config {
var esConfig = EventSource.Config(handler: CallbackHandler(baseUrl: callbackUrl), url: streamUrl)
if let initialDelayMs = initialDelayMs { esConfig.reconnectTime = Double(initialDelayMs) / 1000.0 }
if let readTimeoutMs = readTimeoutMs { esConfig.idleTimeout = Double(readTimeoutMs) / 1000.0 }
if let lastEventId = lastEventId { esConfig.lastEventId = lastEventId }
if let headers = headers { esConfig.headers = headers }
if let method = method { esConfig.method = method }
if let body = body { esConfig.body = Data(body.utf8) }
return esConfig
}
}
class CallbackHandler: EventHandler {
struct EventPayloadEvent: Encodable {
let type: String
let data: String
let id: String?
}
struct EventPayload: Encodable {
let kind = "event"
let event: EventPayloadEvent
}
struct CommentPayload: Encodable {
let kind = "comment"
let comment: String
}
struct ErrorPayload: Encodable {
let kind = "error"
}
let baseUrl: URL
var count = 0
init(baseUrl: URL) {
self.baseUrl = baseUrl
}
func onOpened() { }
func onClosed() { }
func sendUpdate<T: Encodable>(_ update: T) {
count += 1
var request = URLRequest(url: baseUrl.appendingPathComponent(String(count), isDirectory: false))
request.httpMethod = "POST"
let data = try! JSONEncoder().encode(update)
URLSession.shared.uploadTask(with: request, from: data) { _, _, _ in }.resume()
}
func onMessage(eventType type: String, messageEvent msg: MessageEvent) {
sendUpdate(EventPayload(event: EventPayloadEvent(type: type, data: msg.data, id: msg.lastEventId)))
}
func onComment(comment: String) {
sendUpdate(CommentPayload(comment: comment))
}
func onError(error: Error) {
sendUpdate(ErrorPayload())
}
}
let stateQueue = DispatchQueue(label: "StateQueue")
var nextId: Int = 0
var state: [String: EventSource] = [:]
let router = Router()
router.get("/") { _, resp, next in
resp.send(StatusResp())
next()
}
router.delete("/") { _, resp, next in
resp.send(["message": "Shutting down contract test service"])
next()
Kitura.stop()
}
router.post("/") { req, resp, next in
guard let createStreamReq = try? req.read(as: CreateStreamReq.self)
else {
resp.status(.badRequest).send(["message": "Body of POST to '/' invalid"])
return next()
}
let es = EventSource(config: createStreamReq.createEventSourceConfig())
let location: String = stateQueue.sync {
state[String(nextId)] = es
nextId += 1
return "/control/\(nextId - 1)"
}
es.start()
resp.headers["Location"] = location
resp.send(["message": "Created test service entity at \(location)"])
next()
}
router.delete("/control/:id") { req, resp, next in
stateQueue.sync {
if let es = state.removeValue(forKey: req.parameters["id"]!) {
es.stop()
resp.send(["message": "Shut down test service entity at \(req.matchedPath)"])
} else {
resp.status(.notFound).send(["message": "Test service entity not found at \(req.matchedPath)"])
}
}
next()
}
Kitura.addHTTPServer(onPort: 8000, onAddress: "localhost", with: router)
Kitura.run()
================================================
FILE: LDSwiftEventSource.podspec
================================================
Pod::Spec.new do |s|
s.name = "LDSwiftEventSource"
s.version = "3.3.0" # x-release-please-version
s.summary = "Swift EventSource library"
s.homepage = "https://github.com/launchdarkly/swift-eventsource"
s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE.txt" }
s.author = { "LaunchDarkly" => "sdks@launchdarkly.com" }
s.ios.deployment_target = "11.0"
s.watchos.deployment_target = "4.0"
s.tvos.deployment_target = "11.0"
s.osx.deployment_target = "10.13"
s.source = { :git => s.homepage + '.git', :tag => s.version}
s.source_files = "Source/**/*.swift"
s.swift_versions = ['5.0', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6', '5.7']
end
================================================
FILE: LDSwiftEventSource.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
B426585E272849AF007B711A /* MockHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B426585D272849AF007B711A /* MockHandler.swift */; };
B495D4A9248652DF00AE9233 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = B495D4A7248652DF00AE9233 /* Types.swift */; };
B49B5E4B24667F62008BF867 /* UTF8LineParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49B5E4524667F43008BF867 /* UTF8LineParser.swift */; };
B49B5E4C24667F62008BF867 /* EventParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49B5E4624667F43008BF867 /* EventParser.swift */; };
B49B5E4D24667F62008BF867 /* LDSwiftEventSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49B5E4724667F43008BF867 /* LDSwiftEventSource.swift */; };
B49B5E5824668031008BF867 /* EventParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49B5E4024667F43008BF867 /* EventParserTests.swift */; };
B49B5E5A24668031008BF867 /* UTF8LineParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49B5E4224667F43008BF867 /* UTF8LineParserTests.swift */; };
B49B5E5B24668031008BF867 /* LDSwiftEventSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49B5E4324667F43008BF867 /* LDSwiftEventSourceTests.swift */; };
B49B5E67246684B9008BF867 /* LDSwiftEventSource.h in Headers */ = {isa = PBXBuildFile; fileRef = B49B5E65246684B9008BF867 /* LDSwiftEventSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
B49B5E72246C4796008BF867 /* LDSwiftEventSource.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B49B5DFC24667D41008BF867 /* LDSwiftEventSource.framework */; };
B4BCAE6E272753FA000EBD43 /* TestUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4BCAE6D272753FA000EBD43 /* TestUtil.swift */; };
B4C29CC826FF743D008B6DE2 /* Logs.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C29CC726FF743C008B6DE2 /* Logs.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
B49B5E0624667D42008BF867 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = B49B5DB824667C44008BF867 /* Project object */;
proxyType = 1;
remoteGlobalIDString = B49B5DFB24667D41008BF867;
remoteInfo = "LDSwiftEventSource macOS";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
B426585D272849AF007B711A /* MockHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockHandler.swift; sourceTree = "<group>"; };
B495D4A7248652DF00AE9233 /* Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = "<group>"; };
B49B5DE324667D06008BF867 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B49B5DFC24667D41008BF867 /* LDSwiftEventSource.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LDSwiftEventSource.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B49B5E0424667D42008BF867 /* LDSwiftEventSource Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LDSwiftEventSource Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
B49B5E4024667F43008BF867 /* EventParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventParserTests.swift; sourceTree = "<group>"; };
B49B5E4224667F43008BF867 /* UTF8LineParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTF8LineParserTests.swift; sourceTree = "<group>"; };
B49B5E4324667F43008BF867 /* LDSwiftEventSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDSwiftEventSourceTests.swift; sourceTree = "<group>"; };
B49B5E4524667F43008BF867 /* UTF8LineParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTF8LineParser.swift; sourceTree = "<group>"; };
B49B5E4624667F43008BF867 /* EventParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventParser.swift; sourceTree = "<group>"; };
B49B5E4724667F43008BF867 /* LDSwiftEventSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDSwiftEventSource.swift; sourceTree = "<group>"; };
B49B5E65246684B9008BF867 /* LDSwiftEventSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LDSwiftEventSource.h; sourceTree = "<group>"; };
B49B5E6B2466875F008BF867 /* LDSwiftEventSource.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = LDSwiftEventSource.podspec; sourceTree = "<group>"; };
B49B5E6C2466875F008BF867 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
B49B5E6D2466875F008BF867 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = "<group>"; };
B49B5E6E2466875F008BF867 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = "<group>"; };
B49B5E6F2466875F008BF867 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
B49B5E702466875F008BF867 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
B4BCAE6D272753FA000EBD43 /* TestUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtil.swift; sourceTree = "<group>"; };
B4C29CC726FF743C008B6DE2 /* Logs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logs.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
B49B5DF924667D41008BF867 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B49B5E0124667D42008BF867 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B49B5E72246C4796008BF867 /* LDSwiftEventSource.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
B49B5DB724667C44008BF867 = {
isa = PBXGroup;
children = (
B49B5E6A2466873F008BF867 /* Misc */,
B49B5E4424667F43008BF867 /* Source */,
B49B5E3F24667F43008BF867 /* Tests */,
B49B5DC224667C44008BF867 /* Products */,
);
sourceTree = "<group>";
};
B49B5DC224667C44008BF867 /* Products */ = {
isa = PBXGroup;
children = (
B49B5DFC24667D41008BF867 /* LDSwiftEventSource.framework */,
B49B5E0424667D42008BF867 /* LDSwiftEventSource Tests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
B49B5E3F24667F43008BF867 /* Tests */ = {
isa = PBXGroup;
children = (
B49B5E4024667F43008BF867 /* EventParserTests.swift */,
B49B5E4224667F43008BF867 /* UTF8LineParserTests.swift */,
B49B5E4324667F43008BF867 /* LDSwiftEventSourceTests.swift */,
B4BCAE6D272753FA000EBD43 /* TestUtil.swift */,
B426585D272849AF007B711A /* MockHandler.swift */,
);
path = Tests;
sourceTree = "<group>";
};
B49B5E4424667F43008BF867 /* Source */ = {
isa = PBXGroup;
children = (
B49B5DE324667D06008BF867 /* Info.plist */,
B49B5E4524667F43008BF867 /* UTF8LineParser.swift */,
B49B5E4624667F43008BF867 /* EventParser.swift */,
B49B5E4724667F43008BF867 /* LDSwiftEventSource.swift */,
B49B5E65246684B9008BF867 /* LDSwiftEventSource.h */,
B495D4A7248652DF00AE9233 /* Types.swift */,
B4C29CC726FF743C008B6DE2 /* Logs.swift */,
);
path = Source;
sourceTree = "<group>";
};
B49B5E6A2466873F008BF867 /* Misc */ = {
isa = PBXGroup;
children = (
B49B5E6C2466875F008BF867 /* CHANGELOG.md */,
B49B5E6D2466875F008BF867 /* CONTRIBUTING.md */,
B49B5E6B2466875F008BF867 /* LDSwiftEventSource.podspec */,
B49B5E6E2466875F008BF867 /* LICENSE.txt */,
B49B5E702466875F008BF867 /* Package.swift */,
B49B5E6F2466875F008BF867 /* README.md */,
);
name = Misc;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
B49B5DF724667D41008BF867 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
B49B5E67246684B9008BF867 /* LDSwiftEventSource.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
B49B5DFB24667D41008BF867 /* LDSwiftEventSource */ = {
isa = PBXNativeTarget;
buildConfigurationList = B49B5E0D24667D42008BF867 /* Build configuration list for PBXNativeTarget "LDSwiftEventSource" */;
buildPhases = (
B46C1C6B24CF348B00283630 /* Linter Script */,
B49B5DF724667D41008BF867 /* Headers */,
B49B5DF824667D41008BF867 /* Sources */,
B49B5DF924667D41008BF867 /* Frameworks */,
B49B5DFA24667D41008BF867 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = LDSwiftEventSource;
productName = "LDSwiftEventSource macOS";
productReference = B49B5DFC24667D41008BF867 /* LDSwiftEventSource.framework */;
productType = "com.apple.product-type.framework";
};
B49B5E0324667D42008BF867 /* LDSwiftEventSource Tests */ = {
isa = PBXNativeTarget;
buildConfigurationList = B49B5E1024667D42008BF867 /* Build configuration list for PBXNativeTarget "LDSwiftEventSource Tests" */;
buildPhases = (
B49B5E0024667D42008BF867 /* Sources */,
B49B5E0124667D42008BF867 /* Frameworks */,
B49B5E0224667D42008BF867 /* Resources */,
);
buildRules = (
);
dependencies = (
B49B5E0724667D42008BF867 /* PBXTargetDependency */,
);
name = "LDSwiftEventSource Tests";
productName = "LDSwiftEventSource macOSTests";
productReference = B49B5E0424667D42008BF867 /* LDSwiftEventSource Tests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
B49B5DB824667C44008BF867 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1140;
LastUpgradeCheck = 1140;
ORGANIZATIONNAME = LaunchDarkly;
TargetAttributes = {
B49B5DFB24667D41008BF867 = {
CreatedOnToolsVersion = 11.4;
};
B49B5E0324667D42008BF867 = {
CreatedOnToolsVersion = 11.4;
};
};
};
buildConfigurationList = B49B5DBB24667C44008BF867 /* Build configuration list for PBXProject "LDSwiftEventSource" */;
compatibilityVersion = "Xcode 10.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = B49B5DB724667C44008BF867;
productRefGroup = B49B5DC224667C44008BF867 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
B49B5DFB24667D41008BF867 /* LDSwiftEventSource */,
B49B5E0324667D42008BF867 /* LDSwiftEventSource Tests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
B49B5DFA24667D41008BF867 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B49B5E0224667D42008BF867 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
B46C1C6B24CF348B00283630 /* Linter Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Linter Script";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Adds support for Apple Silicon brew directory\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which mint >/dev/null; then\n /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint\nelse\n echo \"warning: mint not installed, available from https://github.com/yonaskolb/Mint\"\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
B49B5DF824667D41008BF867 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B49B5E4B24667F62008BF867 /* UTF8LineParser.swift in Sources */,
B4C29CC826FF743D008B6DE2 /* Logs.swift in Sources */,
B495D4A9248652DF00AE9233 /* Types.swift in Sources */,
B49B5E4C24667F62008BF867 /* EventParser.swift in Sources */,
B49B5E4D24667F62008BF867 /* LDSwiftEventSource.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B49B5E0024667D42008BF867 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B49B5E5824668031008BF867 /* EventParserTests.swift in Sources */,
B49B5E5A24668031008BF867 /* UTF8LineParserTests.swift in Sources */,
B426585E272849AF007B711A /* MockHandler.swift in Sources */,
B49B5E5B24668031008BF867 /* LDSwiftEventSourceTests.swift in Sources */,
B4BCAE6E272753FA000EBD43 /* TestUtil.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
B49B5E0724667D42008BF867 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = B49B5DFB24667D41008BF867 /* LDSwiftEventSource */;
targetProxy = B49B5E0624667D42008BF867 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
B49B5DD324667C44008BF867 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
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;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
"COMBINE_HIDPI_IMAGES[sdk=macosx]" = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
FRAMEWORK_VERSION = C;
GCC_C_LANGUAGE_STANDARD = gnu11;
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;
INFOPLIST_FILE = Source/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.launchdarkly.LDSwiftEventSource;
PRODUCT_NAME = LDSwiftEventSource;
SDKROOT = macosx;
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = 11.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
WATCHOS_DEPLOYMENT_TARGET = 4.0;
};
name = Debug;
};
B49B5DD424667C44008BF867 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
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;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
"COMBINE_HIDPI_IMAGES[sdk=macosx]" = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
FRAMEWORK_VERSION = C;
GCC_C_LANGUAGE_STANDARD = gnu11;
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;
INFOPLIST_FILE = Source/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.launchdarkly.LDSwiftEventSource;
PRODUCT_NAME = LDSwiftEventSource;
SDKROOT = macosx;
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = 11.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
WATCHOS_DEPLOYMENT_TARGET = 4.0;
};
name = Release;
};
B49B5E0E24667D42008BF867 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 3.0.0;
DYLIB_CURRENT_VERSION = 3.3.0;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = YES;
"ENABLE_BITCODE[sdk=macosx*]" = NO;
INFOPLIST_FILE = "$(inherited)";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 3.3.0;
SKIP_INSTALL = YES;
"TARGETED_DEVICE_FAMILY[sdk=appletvos*]" = 3;
"TARGETED_DEVICE_FAMILY[sdk=appletvsimulator*]" = 3;
"TARGETED_DEVICE_FAMILY[sdk=iphoneos*]" = "1,2";
"TARGETED_DEVICE_FAMILY[sdk=iphonesimulator*]" = "1,2";
"TARGETED_DEVICE_FAMILY[sdk=watchos*]" = 4;
"TARGETED_DEVICE_FAMILY[sdk=watchsimulator*]" = 4;
};
name = Debug;
};
B49B5E0F24667D42008BF867 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 3.0.0;
DYLIB_CURRENT_VERSION = 3.3.0;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = YES;
"ENABLE_BITCODE[sdk=macosx*]" = NO;
INFOPLIST_FILE = "$(inherited)";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 3.3.0;
SKIP_INSTALL = YES;
"TARGETED_DEVICE_FAMILY[sdk=appletvos*]" = 3;
"TARGETED_DEVICE_FAMILY[sdk=appletvsimulator*]" = 3;
"TARGETED_DEVICE_FAMILY[sdk=iphoneos*]" = "1,2";
"TARGETED_DEVICE_FAMILY[sdk=iphonesimulator*]" = "1,2";
"TARGETED_DEVICE_FAMILY[sdk=watchos*]" = 4;
"TARGETED_DEVICE_FAMILY[sdk=watchsimulator*]" = 4;
};
name = Release;
};
B49B5E1124667D42008BF867 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = "$(inherited)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.launchdarkly.LDSwiftEventSourceTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator";
};
name = Debug;
};
B49B5E1224667D42008BF867 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = "$(inherited)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.launchdarkly.LDSwiftEventSourceTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
B49B5DBB24667C44008BF867 /* Build configuration list for PBXProject "LDSwiftEventSource" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B49B5DD324667C44008BF867 /* Debug */,
B49B5DD424667C44008BF867 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B49B5E0D24667D42008BF867 /* Build configuration list for PBXNativeTarget "LDSwiftEventSource" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B49B5E0E24667D42008BF867 /* Debug */,
B49B5E0F24667D42008BF867 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B49B5E1024667D42008BF867 /* Build configuration list for PBXNativeTarget "LDSwiftEventSource Tests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B49B5E1124667D42008BF867 /* Debug */,
B49B5E1224667D42008BF867 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = B49B5DB824667C44008BF867 /* Project object */;
}
================================================
FILE: LDSwiftEventSource.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:LDSwiftEventSource.xcodeproj">
</FileRef>
</Workspace>
================================================
FILE: LDSwiftEventSource.xcodeproj/xcshareddata/xcschemes/LDSwiftEventSource.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1140"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B49B5DFB24667D41008BF867"
BuildableName = "LDSwiftEventSource.framework"
BlueprintName = "LDSwiftEventSource"
ReferencedContainer = "container:LDSwiftEventSource.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
systemAttachmentLifetime = "keepNever"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B49B5E0324667D42008BF867"
BuildableName = "LDSwiftEventSource Tests.xctest"
BlueprintName = "LDSwiftEventSource Tests"
ReferencedContainer = "container:LDSwiftEventSource.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B49B5DFB24667D41008BF867"
BuildableName = "LDSwiftEventSource.framework"
BlueprintName = "LDSwiftEventSource"
ReferencedContainer = "container:LDSwiftEventSource.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B49B5DFB24667D41008BF867"
BuildableName = "LDSwiftEventSource.framework"
BlueprintName = "LDSwiftEventSource"
ReferencedContainer = "container:LDSwiftEventSource.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: LICENSE.txt
================================================
Copyright 2020 Catamorphic, Co.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
build:
swift build
clean:
swift clean
test:
swift test
TEMP_TEST_OUTPUT=/tmp/sse-contract-test-service.log
build-contract-tests:
cd ContractTestService && swift build
start-contract-test-service:
./ContractTestService/.build/debug/contract-test-service
start-contract-test-service-bg:
echo "Test service output will be captured in $(TEMP_TEST_OUTPUT)"
make start-contract-test-service >$(TEMP_TEST_OUTPUT) 2>&1 &
run-contract-tests:
curl -s https://raw.githubusercontent.com/launchdarkly/sse-contract-tests/main/downloader/run.sh \
| VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end -skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks'" sh
contract-tests: build-contract-tests start-contract-test-service-bg run-contract-tests
.PHONY: build clean test build-contract-tests start-contract-test-service run-contract-tests contract-tests
================================================
FILE: Package.swift
================================================
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "LDSwiftEventSource",
platforms: [
.iOS(.v11),
.macOS(.v10_13),
.watchOS(.v4),
.tvOS(.v11)
],
products: [
.library(name: "LDSwiftEventSource", targets: ["LDSwiftEventSource"]),
],
dependencies: [],
targets: [
.target(
name: "LDSwiftEventSource",
path: "Source"),
.testTarget(
name: "LDSwiftEventSourceTests",
dependencies: ["LDSwiftEventSource"],
path: "Tests"),
],
swiftLanguageVersions: [.v5])
================================================
FILE: README.md
================================================
# LDSwiftEventSource
[](https://github.com/launchdarkly/swift-eventsource/actions/workflows/ci.yml)
[](https://cocoapods.org/pods/LDSwiftEventSource)
[](https://github.com/Carthage/Carthage)
[](https://swift.org/package-manager/)
[](https://cocoapods.org/pods/LDSwiftEventSource)
LDSwiftEventSource is a cross platform implementation of the [EventSource specification](https://html.spec.whatwg.org/multipage/server-sent-events.html) written in Swift. It was developed for use in the [LaunchDarkly iOS SDK](https://github.com/launchdarkly/ios-client-sdk). Generated API docs are available on [GitHub Pages](https://launchdarkly.github.io/swift-eventsource/).
## Requirements
- iOS 11.0+ / watchOS 4.0+ / tvOS 11.0+ / macOS 10.13+
- Swift 5.1+
## Installation
### CocoaPods
To use the [CocoaPods](https://cocoapods.org) dependency manager to integrate LDSwiftEventSource into your Xcode project, specify it in your `Podfile`:
```ruby
pod 'LDSwiftEventSource', '~> 3.3'
```
### Carthage
To use the [Carthage](https://github.com/Carthage/Carthage) dependency manager to integrate LDSwiftEventSource into your Xcode project, specify it in your `Cartfile`:
```ogdl
github "LaunchDarkly/swift-eventsource" ~> 3.3
```
### Swift Package Manager
The [Swift Package Manager](https://swift.org/package-manager/) is a dependency manager integrated into the `swift` compiler and Xcode. Note that the LDSwiftEventSource Swift package provides both a `LDSwiftEventSource` product, which is explicitly dynamic, and a `LDSwiftEventSourceStatic` product which is explicitly static.
To integrate LDSwiftEventSource into an Xcode project, go to the project editor, and select `Swift Packages`. From here hit the `+` button and follow the prompts using `https://github.com/LaunchDarkly/swift-eventsource.git` as the URL.
To include LDSwiftEventSource in a Swift package, simply add it to the dependencies section of your `Package.swift` file. And add the desired product as a dependency for your targets.
<!-- x-release-please-start-version -->
```swift
dependencies: [
.package(url: "https://github.com/LaunchDarkly/swift-eventsource.git", .upToNextMajor(from: "3.3.0"))
]
```
<!-- x-release-please-end -->
## Contributing
We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](https://github.com/LaunchDarkly/swift-eventsource/blob/main/CONTRIBUTING.md) for instructions on how to contribute to this SDK.
## About LaunchDarkly
* LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
* Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
* Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
* Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
* Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline.
* LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Check out [our documentation](https://docs.launchdarkly.com/sdk) for a complete list.
* Explore LaunchDarkly
* [launchdarkly.com](https://www.launchdarkly.com/ "LaunchDarkly Main Website") for more information
* [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides
* [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation
* [blog.launchdarkly.com](https://blog.launchdarkly.com/ "LaunchDarkly Blog Documentation") for the latest product updates
================================================
FILE: SECURITY.md
================================================
# Reporting and Fixing Security Issues
Please report all security issues to the LaunchDarkly security team by submitting a bug bounty report to our [HackerOne program](https://hackerone.com/launchdarkly?type=team). LaunchDarkly will triage and address all valid security issues following the response targets defined in our program policy. Valid security issues may be eligible for a bounty.
Please do not open issues or pull requests for security issues. This makes the problem immediately visible to everyone, including potentially malicious actors.
================================================
FILE: Source/.swiftlint.yml
================================================
disabled_rules:
opt_in_rules:
- force_unwrapping
- implicitly_unwrapped_optional
================================================
FILE: Source/EventParser.swift
================================================
import Foundation
class EventParser {
private struct Constants {
static let dataLabel: Substring = "data"
static let idLabel: Substring = "id"
static let eventLabel: Substring = "event"
static let retryLabel: Substring = "retry"
}
private let handler: EventHandler
private var data: String = ""
private var eventType: String = ""
private var lastEventIdBuffer: String?
private var lastEventId: String
private var currentRetry: TimeInterval
init(handler: EventHandler, initialEventId: String, initialRetry: TimeInterval) {
self.handler = handler
self.lastEventId = initialEventId
self.currentRetry = initialRetry
}
func parse(line: String) {
let splitByColon = line.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false)
switch (splitByColon[0], splitByColon[safe: 1]) {
case ("", nil): // Empty line
dispatchEvent()
case let ("", .some(comment)): // Line starting with ':' is a comment
handler.onComment(comment: String(comment))
case let (field, data):
processField(field: field, value: dropLeadingSpace(str: data ?? ""))
}
}
func getLastEventId() -> String { lastEventId }
func reset() -> TimeInterval {
data = ""
eventType = ""
lastEventIdBuffer = nil
return currentRetry
}
private func dropLeadingSpace(str: Substring) -> Substring {
if str.first == " " {
return str[str.index(after: str.startIndex)...]
}
return str
}
private func processField(field: Substring, value: Substring) {
switch field {
case Constants.dataLabel:
data.append(contentsOf: value)
data.append(contentsOf: "\n")
case Constants.idLabel:
// See https://github.com/whatwg/html/issues/689 for reasoning on not setting lastEventId if the value
// contains a null code point.
if !value.contains("\u{0000}") {
lastEventIdBuffer = String(value)
}
case Constants.eventLabel:
eventType = String(value)
case Constants.retryLabel:
if value.allSatisfy(("0"..."9").contains), let reconnectionTime = Int64(value) {
currentRetry = Double(reconnectionTime) * 0.001
}
default:
break
}
}
private func dispatchEvent() {
lastEventId = lastEventIdBuffer ?? lastEventId
lastEventIdBuffer = nil
guard !data.isEmpty
else {
eventType = ""
return
}
// remove the last LF
_ = data.popLast()
let messageEvent = MessageEvent(data: data, lastEventId: lastEventId)
handler.onMessage(eventType: eventType.isEmpty ? "message" : eventType, messageEvent: messageEvent)
data = ""
eventType = ""
}
}
private extension Array {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
index >= startIndex && index < endIndex ? self[index] : nil
}
}
================================================
FILE: Source/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>
================================================
FILE: Source/LDSwiftEventSource.h
================================================
@import Foundation;
FOUNDATION_EXPORT double LDSwiftEventSourceVersionNumber;
FOUNDATION_EXPORT const unsigned char LDSwiftEventSourceVersionString[];
================================================
FILE: Source/LDSwiftEventSource.swift
================================================
import Foundation
#if os(Linux) || os(Windows)
import FoundationNetworking
#endif
#if canImport(os)
// os_log is not supported on some platforms, but we want to use it for most of our customer's
// use cases that use Apple OSs
import os.log
#endif
/**
Provides an EventSource client for consuming Server-Sent Events.
See the [Server-Sent Events spec](https://html.spec.whatwg.org/multipage/server-sent-events.html) for more details.
*/
public class EventSource {
private let esDelegate: EventSourceDelegate
/**
Initialize the `EventSource` client with the given configuration.
- Parameter config: The configuration for initializing the `EventSource` client.
*/
public init(config: Config) {
esDelegate = EventSourceDelegate(config: config)
}
/**
Start the `EventSource` client.
This will initiate a streaming connection to the configured URL. The application will be informed of received
events and state changes using the configured `EventHandler`.
*/
public func start() {
esDelegate.start()
}
/// Shuts down the `EventSource` client. It is not valid to restart the client after calling this function.
public func stop() {
esDelegate.stop()
}
/// Get the most recently received event ID, or the value of `EventSource.Config.lastEventId` if no event IDs have
/// been received.
public func getLastEventId() -> String? { esDelegate.getLastEventId() }
/// Struct for configuring the EventSource.
public struct Config {
/// The `EventHandler` called in response to activity on the stream.
public let handler: EventHandler
/// The `URL` of the request used when connecting to the EventSource API.
public let url: URL
/// The HTTP method to use for the API request.
public var method: String = "GET"
/// Optional HTTP body to be included in the API request.
public var body: Data?
/// Additional HTTP headers to be set on the request
public var headers: [String: String] = [:]
/// Transform function to allow dynamically configuring the headers on each API request.
public var headerTransform: HeaderTransform = { $0 }
/// An initial value for the last-event-id header to be sent on the initial request
public var lastEventId: String = ""
#if canImport(os)
/// Configure the logger that will be used.
public var logger: OSLog = OSLog(subsystem: "com.launchdarkly.swift-eventsource", category: "LDEventSource")
#endif
/// The minimum amount of time to wait before reconnecting after a failure
public var reconnectTime: TimeInterval = 1.0
/// The maximum amount of time to wait before reconnecting after a failure
public var maxReconnectTime: TimeInterval = 30.0
/// The minimum amount of time for an `EventSource` connection to remain open before allowing the connection
/// backoff to reset.
public var backoffResetThreshold: TimeInterval = 60.0
/// The maximum amount of time between receiving any data before considering the connection to have timed out.
public var idleTimeout: TimeInterval = 300.0
private var _urlSessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.default
/**
The `URLSessionConfiguration` used to create the `URLSession`.
- Important:
Note that this copies the given `URLSessionConfiguration` when set, and returns copies (updated with any
overrides specified by other configuration options) when the value is retrieved. This prevents updating the
`URLSessionConfiguration` after initializing `EventSource` with the `Config`, and prevents the `EventSource`
from updating any properties of the given `URLSessionConfiguration`.
- Since: 1.3.0
*/
public var urlSessionConfiguration: URLSessionConfiguration {
get {
// swiftlint:disable:next force_cast
let sessionConfig = _urlSessionConfiguration.copy() as! URLSessionConfiguration
sessionConfig.httpAdditionalHeaders = ["Accept": "text/event-stream", "Cache-Control": "no-cache"]
sessionConfig.timeoutIntervalForRequest = idleTimeout
#if !os(Linux) && !os(Windows)
if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) {
sessionConfig.tlsMinimumSupportedProtocolVersion = .TLSv12
} else {
sessionConfig.tlsMinimumSupportedProtocol = .tlsProtocol12
}
#endif
return sessionConfig
}
set {
// swiftlint:disable:next force_cast
_urlSessionConfiguration = newValue.copy() as! URLSessionConfiguration
}
}
/**
An error handler that is called when an error occurs and can shut down the client in response.
The default error handler will always attempt to reconnect on an
error, unless `EventSource.stop()` is called or the error code is 204.
*/
public var connectionErrorHandler: ConnectionErrorHandler = { error in
guard let unsuccessfulResponseError = error as? UnsuccessfulResponseError
else { return .proceed }
let responseCode: Int = unsuccessfulResponseError.responseCode
if 204 == responseCode {
return .shutdown
}
return .proceed
}
/// Create a new configuration with an `EventHandler` and a `URL`
public init(handler: EventHandler, url: URL) {
self.handler = handler
self.url = url
}
}
}
class ReconnectionTimer {
private let maxDelay: TimeInterval
private let resetInterval: TimeInterval
var backoffCount: Int = 0
var connectedTime: Date?
init(maxDelay: TimeInterval, resetInterval: TimeInterval) {
self.maxDelay = maxDelay
self.resetInterval = resetInterval
}
func reconnectDelay(baseDelay: TimeInterval) -> TimeInterval {
backoffCount += 1
if let connectedTime = connectedTime, Date().timeIntervalSince(connectedTime) >= resetInterval {
backoffCount = 0
}
self.connectedTime = nil
let maxSleep = min(maxDelay, baseDelay * pow(2.0, Double(backoffCount)))
return maxSleep / 2 + Double.random(in: 0...(maxSleep / 2))
}
}
// MARK: EventSourceDelegate
class EventSourceDelegate: NSObject, URLSessionDataDelegate {
private let delegateQueue: DispatchQueue = DispatchQueue(label: "ESDelegateQueue")
public var logger: InternalLogging
private let config: EventSource.Config
private var readyState: ReadyState = .raw {
didSet {
logger.log(.debug, "State: %@ -> %@", oldValue.rawValue, readyState.rawValue)
}
}
private let utf8LineParser: UTF8LineParser = UTF8LineParser()
private let eventParser: EventParser
private let reconnectionTimer: ReconnectionTimer
private var urlSession: URLSession?
private var sessionTask: URLSessionDataTask?
init(config: EventSource.Config) {
self.config = config
#if canImport(os)
self.logger = OSLogAdapter(osLog: config.logger)
#else
self.logger = NoOpLogging()
#endif
self.eventParser = EventParser(handler: config.handler,
initialEventId: config.lastEventId,
initialRetry: config.reconnectTime)
self.reconnectionTimer = ReconnectionTimer(maxDelay: config.maxReconnectTime,
resetInterval: config.backoffResetThreshold)
}
func start() {
delegateQueue.async { [weak self] in
guard let self = self
else { return }
guard self.readyState == .raw
else {
self.logger.log(.info, "start() called on already-started EventSource object. Returning")
return
}
self.readyState = .connecting
self.urlSession = self.createSession()
self.connect()
}
}
func stop() {
delegateQueue.async {
let previousState = self.readyState
self.readyState = .shutdown
self.sessionTask?.cancel()
if previousState == .open {
self.config.handler.onClosed()
}
self.urlSession?.invalidateAndCancel()
self.urlSession = nil
}
}
func getLastEventId() -> String { eventParser.getLastEventId() }
func createSession() -> URLSession {
let opQueue = OperationQueue()
opQueue.underlyingQueue = self.delegateQueue
return URLSession(configuration: config.urlSessionConfiguration, delegate: self, delegateQueue: opQueue)
}
func createRequest() -> URLRequest {
var urlRequest = URLRequest(url: self.config.url,
cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: self.config.idleTimeout)
urlRequest.httpMethod = self.config.method
urlRequest.httpBody = self.config.body
if !eventParser.getLastEventId().isEmpty {
urlRequest.setValue(eventParser.getLastEventId(), forHTTPHeaderField: "Last-Event-Id")
}
urlRequest.allHTTPHeaderFields = self.config.headerTransform(
urlRequest.allHTTPHeaderFields?.merging(self.config.headers) { $1 } ?? self.config.headers
)
return urlRequest
}
private func connect() {
logger.log(.info, "Starting EventSource client")
let task = urlSession?.dataTask(with: createRequest())
task?.resume()
sessionTask = task
}
func dispatchError(error: Error) -> ConnectionErrorAction {
let action: ConnectionErrorAction = config.connectionErrorHandler(error)
if action != .shutdown {
config.handler.onError(error: error)
}
return action
}
// MARK: URLSession Delegates
// Tells the delegate that the task finished transferring data.
public func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?) {
utf8LineParser.closeAndReset()
let currentRetry = eventParser.reset()
guard readyState != .shutdown
else { return }
if let error = error {
if (error as NSError).code != NSURLErrorCancelled {
logger.log(.info, "Connection error: %@", error.localizedDescription)
if dispatchError(error: error) == .shutdown {
logger.log(.info, "Connection has been explicitly shut down by error handler")
if readyState == .open {
config.handler.onClosed()
}
readyState = .shutdown
return
}
}
} else {
logger.log(.info, "Connection unexpectedly closed.")
}
if readyState == .open {
config.handler.onClosed()
}
readyState = .closed
let sleep = reconnectionTimer.reconnectDelay(baseDelay: currentRetry)
// this formatting shenanigans is to workaround String not implementing CVarArg on Swift<5.4 on Linux
logger.log(.info, "Waiting %@ seconds before reconnecting...", String(format: "%.3f", sleep))
delegateQueue.asyncAfter(deadline: .now() + sleep) { [weak self] in
self?.connect()
}
}
// Tells the delegate that the data task received the initial reply (headers) from the server.
public func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
logger.log(.debug, "Initial reply received")
guard readyState != .shutdown
else {
completionHandler(.cancel)
return
}
// swiftlint:disable:next force_cast
let httpResponse = response as! HTTPURLResponse
let statusCode = httpResponse.statusCode
if (200..<300).contains(statusCode) && statusCode != 204 {
reconnectionTimer.connectedTime = Date()
readyState = .open
config.handler.onOpened()
completionHandler(.allow)
} else {
// this formatting shenanigans is to workaround String not implementing CVarArg on Swift<5.4 on Linux
logger.log(.info, "Unsuccessful response: %@", String(format: "%d", statusCode))
if dispatchError(error: UnsuccessfulResponseError(responseCode: statusCode)) == .shutdown {
logger.log(.info, "Connection has been explicitly shut down by error handler")
readyState = .shutdown
}
completionHandler(.cancel)
}
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
utf8LineParser.append(data).forEach(eventParser.parse)
}
}
================================================
FILE: Source/Logs.swift
================================================
import Foundation
#if canImport(os)
import os.log
#endif
protocol InternalLogging {
func log(_ level: Level, _ staticMsg: StaticString)
func log(_ level: Level, _ staticMsg: StaticString, _ arg: String)
func log(_ level: Level, _ staticMsg: StaticString, _ arg1: String, _ arg2: String)
}
enum Level {
case debug, info, warn, error
#if canImport(os)
private static let osLogTypes = [ Level.debug: OSLogType.debug,
Level.info: OSLogType.info,
Level.warn: OSLogType.default,
Level.error: OSLogType.error]
var osLogType: OSLogType { Level.osLogTypes[self]! }
#endif
}
#if canImport(os)
class OSLogAdapter: InternalLogging {
private let osLog: OSLog
init(osLog: OSLog) {
self.osLog = osLog
}
func log(_ level: Level, _ staticMsg: StaticString) {
os_log(staticMsg, log: self.osLog, type: level.osLogType)
}
func log(_ level: Level, _ staticMsg: StaticString, _ arg: String) {
os_log(staticMsg, log: self.osLog, type: level.osLogType, arg)
}
func log(_ level: Level, _ staticMsg: StaticString, _ arg1: String, _ arg2: String) {
os_log(staticMsg, log: self.osLog, type: level.osLogType, arg1, arg2)
}
}
#endif
class NoOpLogging: InternalLogging {
func log(_ level: Level, _ staticMsg: StaticString) {}
func log(_ level: Level, _ staticMsg: StaticString, _ arg: String) {}
func log(_ level: Level, _ staticMsg: StaticString, _ arg1: String, _ arg2: String) {}
}
================================================
FILE: Source/Types.swift
================================================
import Foundation
/**
Type for a function that will be notified when the `EventSource` client encounters a connection failure.
This is different from `EventHandler.onError(error:)` in that it will not be called for other kinds of errors; also,
it has the ability to tell the client to stop reconnecting by returning a `ConnectionErrorAction.shutdown`.
*/
public typealias ConnectionErrorHandler = (Error) -> ConnectionErrorAction
/**
Type for a function that will take in the current HTTP headers and return a new set of HTTP headers to be used when
connecting and reconnecting to a stream.
*/
public typealias HeaderTransform = ([String: String]) -> [String: String]
/// Potential actions a `ConnectionErrorHandler` can return
public enum ConnectionErrorAction {
/**
Specifies that the error should be logged normally and dispatched to the `EventHandler`. Connection retrying will
proceed normally if appropriate.
*/
case proceed
/**
Specifies that the connection should be immediately shut down and not retried. The error will not be dispatched
to the `EventHandler`
*/
case shutdown
}
/// Struct representing received event from the stream.
public struct MessageEvent: Equatable, Hashable {
/// The event data of the event.
public let data: String
/// The last seen event id, or the event id set in the Config if none have been received.
public let lastEventId: String
/**
Constructor for a `MessageEvent`
- Parameter data: The `data` field of the `MessageEvent`.
- Parameter eventType: The `lastEventId` field of the `MessageEvent`.
*/
public init(data: String, lastEventId: String = "") {
self.data = data
self.lastEventId = lastEventId
}
}
/// Protocol for an object that will receive SSE events.
public protocol EventHandler {
/// EventSource calls this method when the stream connection has been opened.
func onOpened()
/// EventSource calls this method when the stream connection has been closed.
func onClosed()
/**
EventSource calls this method when it has received a new event from the stream.
- Parameter eventType: The type of the event.
- Parameter messageEvent: The data for the event.
*/
func onMessage(eventType: String, messageEvent: MessageEvent)
/**
EventSource calls this method when it has received a comment line from the stream.
- Parameter comment: The comment received.
*/
func onComment(comment: String)
/**
This method will be called for all exceptions that occur on the network connection (including an
`UnsuccessfulResponseError` if the server returns an unexpected HTTP status), but only after the
ConnectionErrorHandler (if any) has processed it. If you need to do anything that affects the state of the
connection, use ConnectionErrorHandler.
- Parameter error: The error received.
*/
func onError(error: Error)
}
/// Enum values representing the states of an EventSource
public enum ReadyState: String, Equatable {
/// The `EventSource` client has not been started yet.
case raw
/// The `EventSource` client is attempting to make a connection.
case connecting
/// The `EventSource` client is active and listening for events.
case open
/// The connection has been closed or has failed, and the `EventSource` will attempt to reconnect.
case closed
/// The connection has been permanently closed and the `EventSource` not reconnect.
case shutdown
}
/// Error class that indicates the remote server returned an unsuccessful HTTP response code.
public class UnsuccessfulResponseError: Error {
/// The HTTP response code received.
public let responseCode: Int
/**
Constructor for an `UnsuccessfulResponseError`.
- Parameter responseCode: The HTTP response code of the unsuccessful response.
*/
public init(responseCode: Int) {
self.responseCode = responseCode
}
}
================================================
FILE: Source/UTF8LineParser.swift
================================================
import Foundation
struct DataIter: IteratorProtocol {
var data: Data
var position: Data.Index { data.startIndex }
mutating func next() -> UInt8? {
data.popFirst()
}
}
class UTF8LineParser {
private let lf = Unicode.Scalar(0x0A)
private let cr = Unicode.Scalar(0x0D)
private let replacement = String(Unicode.UTF8.decode(Unicode.UTF8.encodedReplacementCharacter))
var utf8Parser = Unicode.UTF8.ForwardParser()
var remainder: Data = Data()
var currentString: String = ""
var seenCr = false
func append(_ body: Data) -> [String] {
let data = remainder + body
var dataIter = DataIter(data: data)
var remainderPos = data.endIndex
var lines: [String] = []
Decode: while true {
switch utf8Parser.parseScalar(from: &dataIter) {
case .valid(let scalarResult):
let scalar = Unicode.UTF8.decode(scalarResult)
if seenCr && scalar == lf {
seenCr = false
continue
}
seenCr = scalar == cr
if scalar == cr || scalar == lf {
lines.append(currentString)
currentString = ""
} else {
currentString.append(String(scalar))
}
case .emptyInput:
break Decode
case .error(let len):
seenCr = false
if dataIter.position == data.endIndex {
// Error at end of block, carry over in case of split code point
remainderPos = data.index(data.endIndex, offsetBy: -len)
// May as well break here as next will be .emptyInput
break Decode
} else {
// Invalid character, replace with replacement character
currentString.append(replacement)
}
}
}
remainder = data.subdata(in: remainderPos..<data.endIndex)
return lines
}
func closeAndReset() {
seenCr = false
currentString = ""
remainder = Data()
}
}
================================================
FILE: Tests/.swiftlint.yml
================================================
disabled_rules:
opt_in_rules:
# Must specify even though enabled by default to update configuration of rule below.
- line_length
- type_body_length
# Provide a little extra lenience for test code line and body length.
line_length:
warning: 140
type_body_length:
warning: 400
error: 500
excluded:
# Autogenerated manifest of tests
- XCTestManifests.swift
================================================
FILE: Tests/EventParserTests.swift
================================================
import XCTest
@testable import LDSwiftEventSource
final class EventParserTests: XCTestCase {
var handler: MockHandler!
var parser: EventParser!
override func setUp() {
super.setUp()
handler = MockHandler()
parser = EventParser(handler: handler, initialEventId: "", initialRetry: 1.0)
}
override func tearDown() {
super.tearDown()
XCTAssertNil(handler.events.maybeEvent())
}
// MARK: Retry time tests
func testUnsetRetryReturnsConfigured() {
parser = EventParser(handler: handler, initialEventId: "", initialRetry: 5.0)
XCTAssertEqual(parser.reset(), 5.0)
}
func testSetsRetryTimeToSevenSeconds() {
parser.parse(line: "retry: 7000")
XCTAssertEqual(parser.reset(), 7.0)
XCTAssertEqual(parser.getLastEventId(), "")
}
func testRetryWithNoSpace() {
parser.parse(line: "retry:7000")
XCTAssertEqual(parser.reset(), 7.0)
XCTAssertEqual(parser.getLastEventId(), "")
}
func testDoesNotSetRetryTimeUnlessEntireValueIsNumeric() {
parser.parse(line: "retry: 7000L")
XCTAssertEqual(parser.reset(), 1.0)
}
func testSafeToUseEmptyRetryTime() {
parser.parse(line: "retry")
XCTAssertEqual(parser.reset(), 1.0)
}
func testSafeToAttemptToSetRetryToOutOfBoundsValue() {
parser.parse(line: "retry: 10000000000000000000000000")
XCTAssertEqual(parser.reset(), 1.0)
}
func testResetDoesNotResetRetry() {
parser.parse(line: "retry: 7000")
XCTAssertEqual(parser.reset(), 7.0)
XCTAssertEqual(parser.reset(), 7.0)
}
func testRetryNotChangedDuringOtherMessages() {
parser.parse(line: "retry: 7000")
parser.parse(line: "")
parser.parse(line: ":123")
parser.parse(line: "event: 123")
parser.parse(line: "data: 123")
parser.parse(line: "id: 123")
parser.parse(line: "none: 123")
parser.parse(line: "")
XCTAssertEqual(parser.reset(), 7.0)
_ = handler.events.maybeEvent()
_ = handler.events.maybeEvent()
}
// MARK: Comment tests
func testEmptyComment() {
parser.parse(line: ":")
XCTAssertEqual(handler.events.maybeEvent(), .comment(""))
}
func testCommentBody() {
parser.parse(line: ": comment")
XCTAssertEqual(handler.events.maybeEvent(), .comment(" comment"))
}
func testCommentCanContainColon() {
parser.parse(line: ":comment:line")
XCTAssertEqual(handler.events.maybeEvent(), .comment("comment:line"))
}
// MARK: Message data tests
func testDispatchesEmptyMessageData() {
parser.parse(line: "data")
parser.parse(line: "")
parser.parse(line: "data:")
parser.parse(line: "")
parser.parse(line: "data: ")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "", lastEventId: "")))
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "", lastEventId: "")))
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "", lastEventId: "")))
}
func testDoesNotRemoveTrailingSpaceWhenColonNotPresent() {
parser.parse(line: "data ")
parser.parse(line: "")
XCTAssertNil(handler.events.maybeEvent())
}
func testEmptyFirstDataAppendsNewline() {
parser.parse(line: "data:")
parser.parse(line: "data:")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "\n", lastEventId: "")))
}
func testDispatchesSingleLineMessage() {
parser.parse(line: "data: hello")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "hello", lastEventId: "")))
}
func testEmptyDataWithBufferedDataAppendsNewline() {
parser.parse(line: "data: data1")
parser.parse(line: "data: ")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "data1\n", lastEventId: "")))
}
func testDataResetAfterEvent() {
parser.parse(line: "data: hello")
parser.parse(line: "")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "hello", lastEventId: "")))
}
func testRemovesOnlyFirstSpace() {
parser.parse(line: "data: {\"foo\": \"bar baz\"}")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: " {\"foo\": \"bar baz\"}", lastEventId: "")))
}
func testDoesNotRemoveOtherWhitespace() {
parser.parse(line: "data:\t{\"foo\": \"bar baz\"}")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "\t{\"foo\": \"bar baz\"}", lastEventId: "")))
}
func testAllowsNoLeadingSpace() {
parser.parse(line: "data:{\"foo\": \"bar baz\"}")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "{\"foo\": \"bar baz\"}", lastEventId: "")))
}
func testMultipleDataDispatch() {
parser.parse(line: "data: data1")
parser.parse(line: "data: data2")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "data1\ndata2", lastEventId: "")))
}
// MARK: Event type tests
func testDispatchesMessageWithCustomEventType() {
parser.parse(line: "event: customEvent")
parser.parse(line: "data: hello")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("customEvent", MessageEvent(data: "hello", lastEventId: "")))
}
func testCustomEventTypeWithoutSpace() {
parser.parse(line: "event:customEvent")
parser.parse(line: "data: hello")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("customEvent", MessageEvent(data: "hello", lastEventId: "")))
}
func testCustomEventAfterData() {
parser.parse(line: "data: hello")
parser.parse(line: "event: customEvent")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("customEvent", MessageEvent(data: "hello", lastEventId: "")))
}
func testEmptyEventTypesDefaultToMessage() {
["event", "event:", "event: "].forEach {
parser.parse(line: $0)
parser.parse(line: "data: foo")
parser.parse(line: "")
}
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "foo", lastEventId: "")))
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "foo", lastEventId: "")))
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "foo", lastEventId: "")))
}
func testDispatchWithoutDataResetsMessageType() {
parser.parse(line: "event: customEvent")
parser.parse(line: "")
parser.parse(line: "data: foo")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "foo", lastEventId: "")))
}
func testDispatchWithDataResetsMessageType() {
parser.parse(line: "event: customEvent")
parser.parse(line: "data: foo")
parser.parse(line: "")
parser.parse(line: "data: bar")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("customEvent", MessageEvent(data: "foo", lastEventId: "")))
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "bar", lastEventId: "")))
}
// MARK: Last event ID tests
func testLastEventIdNotReturnedUntilDispatch() {
XCTAssertEqual(parser.getLastEventId(), "")
parser.parse(line: "id: 1")
XCTAssertNil(handler.events.maybeEvent())
XCTAssertEqual(parser.getLastEventId(), "")
}
func testRecordsLastEventIdWithoutData() {
parser.parse(line: "id: 1")
parser.parse(line: "")
XCTAssertNil(handler.events.maybeEvent())
XCTAssertEqual(parser.getLastEventId(), "1")
}
func testEventIdIncludedInMessageEvent() {
parser.parse(line: "data: hello")
parser.parse(line: "id: 1")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "hello", lastEventId: "1")))
}
func testReusesEventIdIfNotSet() {
parser.parse(line: "data: hello")
parser.parse(line: "id: reused")
parser.parse(line: "")
parser.parse(line: "data: world")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "hello", lastEventId: "reused")))
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "world", lastEventId: "reused")))
XCTAssertEqual(parser.getLastEventId(), "reused")
}
func testEventIdSetTwiceInEvent() {
parser.parse(line: "id: abc")
parser.parse(line: "id: def")
parser.parse(line: "data")
XCTAssertEqual(parser.getLastEventId(), "")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "", lastEventId: "def")))
XCTAssertEqual(parser.getLastEventId(), "def")
}
func testEventIdContainingNullIgnored() {
parser.parse(line: "id: reused")
parser.parse(line: "id: abc\u{0000}def")
parser.parse(line: "data")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "", lastEventId: "reused")))
XCTAssertEqual(parser.getLastEventId(), "reused")
}
func testResetDoesResetLastEventIdBuffer() {
parser.parse(line: "id: 1")
_ = parser.reset()
parser.parse(line: "data: hello")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "hello", lastEventId: "")))
XCTAssertEqual(parser.getLastEventId(), "")
}
func testResetDoesNotResetLastEventId() {
parser.parse(line: "id: 1")
parser.parse(line: "")
_ = parser.reset()
parser.parse(line: "data: hello")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("message", MessageEvent(data: "hello", lastEventId: "1")))
XCTAssertEqual(parser.getLastEventId(), "1")
}
// MARK: Mixed and other tests
func testRepeatedEmptyLines() {
parser.parse(line: "")
parser.parse(line: "")
parser.parse(line: "")
XCTAssertNil(handler.events.maybeEvent())
}
func testNothingDoneForInvalidFieldName() {
parser.parse(line: "invalid: bar")
XCTAssertNil(handler.events.maybeEvent())
}
func testInvalidFieldNameIgnoredInEvent() {
parser.parse(line: "data: foo")
parser.parse(line: "invalid: bar")
parser.parse(line: "event: msg")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .message("msg", MessageEvent(data: "foo", lastEventId: "")))
}
func testCommentInEvent() {
parser.parse(line: "data: foo")
parser.parse(line: ":bar")
parser.parse(line: "event: msg")
parser.parse(line: "")
XCTAssertEqual(handler.events.maybeEvent(), .comment("bar"))
XCTAssertEqual(handler.events.maybeEvent(), .message("msg", MessageEvent(data: "foo", lastEventId: "")))
}
}
================================================
FILE: Tests/LDSwiftEventSourceTests.swift
================================================
import XCTest
@testable import LDSwiftEventSource
#if os(Linux) || os(Windows)
import FoundationNetworking
#endif
final class LDSwiftEventSourceTests: XCTestCase {
private var mockHandler: MockHandler!
override func setUp() {
super.setUp()
mockHandler = MockHandler()
XCTAssertTrue(URLProtocol.registerClass(MockingProtocol.self))
}
override func tearDown() {
super.tearDown()
URLProtocol.unregisterClass(MockingProtocol.self)
// Enforce that tests consume all mocked network requests
MockingProtocol.requested.expectNoEvent(within: 0.01)
MockingProtocol.resetRequested()
// Enforce that tests consume all calls to the mock handler
mockHandler.events.expectNoEvent(within: 0.01)
mockHandler = nil
}
func testConfigDefaults() {
let url = URL(string: "abc")!
let config = EventSource.Config(handler: mockHandler, url: url)
XCTAssertEqual(config.url, url)
XCTAssertEqual(config.method, "GET")
XCTAssertEqual(config.body, nil)
XCTAssertEqual(config.lastEventId, "")
XCTAssertEqual(config.headers, [:])
XCTAssertEqual(config.reconnectTime, 1.0)
XCTAssertEqual(config.maxReconnectTime, 30.0)
XCTAssertEqual(config.backoffResetThreshold, 60.0)
XCTAssertEqual(config.idleTimeout, 300.0)
XCTAssertEqual(config.headerTransform(["abc": "123"]), ["abc": "123"])
XCTAssertEqual(config.connectionErrorHandler(DummyError()), .proceed)
}
func testConfigModification() {
let url = URL(string: "abc")!
var config = EventSource.Config(handler: mockHandler, url: url)
let testBody = "test data".data(using: .utf8)
let testHeaders = ["Authorization": "basic abc"]
config.method = "REPORT"
config.body = testBody
config.lastEventId = "eventId"
config.headers = testHeaders
config.reconnectTime = 2.0
config.maxReconnectTime = 60.0
config.backoffResetThreshold = 120.0
config.idleTimeout = 180.0
config.headerTransform = { _ in [:] }
config.connectionErrorHandler = { _ in .shutdown }
XCTAssertEqual(config.url, url)
XCTAssertEqual(config.method, "REPORT")
XCTAssertEqual(config.body, testBody)
XCTAssertEqual(config.lastEventId, "eventId")
XCTAssertEqual(config.headers, testHeaders)
XCTAssertEqual(config.headerTransform(config.headers), [:])
XCTAssertEqual(config.reconnectTime, 2.0)
XCTAssertEqual(config.maxReconnectTime, 60.0)
XCTAssertEqual(config.backoffResetThreshold, 120.0)
XCTAssertEqual(config.idleTimeout, 180.0)
XCTAssertEqual(config.connectionErrorHandler(DummyError()), .shutdown)
}
func testConfigUrlSession() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "abc")!)
let defaultSessionConfig = config.urlSessionConfiguration
XCTAssertEqual(defaultSessionConfig.timeoutIntervalForRequest, 300.0)
XCTAssertEqual(defaultSessionConfig.httpAdditionalHeaders?["Accept"] as? String, "text/event-stream")
XCTAssertEqual(defaultSessionConfig.httpAdditionalHeaders?["Cache-Control"] as? String, "no-cache")
// Configuration should return a fresh session configuration each retrieval
XCTAssertTrue(defaultSessionConfig !== config.urlSessionConfiguration)
// Updating idleTimeout should effect session config
config.idleTimeout = 600.0
XCTAssertEqual(config.urlSessionConfiguration.timeoutIntervalForRequest, 600.0)
XCTAssertEqual(defaultSessionConfig.timeoutIntervalForRequest, 300.0)
// Updating returned urlSessionConfiguration without setting should not update the Config until set
let sessionConfig = config.urlSessionConfiguration
sessionConfig.allowsCellularAccess = false
XCTAssertTrue(config.urlSessionConfiguration.allowsCellularAccess)
config.urlSessionConfiguration = sessionConfig
XCTAssertFalse(config.urlSessionConfiguration.allowsCellularAccess)
XCTAssertTrue(sessionConfig !== config.urlSessionConfiguration)
}
func testLastEventIdFromConfig() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "abc")!)
var es = EventSource(config: config)
XCTAssertEqual(es.getLastEventId(), "")
config.lastEventId = "def"
es = EventSource(config: config)
XCTAssertEqual(es.getLastEventId(), "def")
}
func testCreatedSession() {
let config = EventSource.Config(handler: mockHandler, url: URL(string: "abc")!)
let session = EventSourceDelegate(config: config).createSession()
XCTAssertEqual(session.configuration.timeoutIntervalForRequest, config.idleTimeout)
XCTAssertEqual(session.configuration.httpAdditionalHeaders?["Accept"] as? String, "text/event-stream")
XCTAssertEqual(session.configuration.httpAdditionalHeaders?["Cache-Control"] as? String, "no-cache")
}
func testCreateRequest() {
// 192.0.2.1 is assigned as TEST-NET-1 reserved usage.
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://192.0.2.1")!)
// Testing default configs
var request = EventSourceDelegate(config: config).createRequest()
XCTAssertEqual(request.url, config.url)
XCTAssertEqual(request.httpMethod, config.method)
XCTAssertEqual(request.httpBody, config.body)
XCTAssertEqual(request.timeoutInterval, config.idleTimeout)
XCTAssertEqual(request.allHTTPHeaderFields, config.headers)
// Testing customized configs
let testBody = "test data".data(using: .utf8)
let testHeaders = ["removing": "a", "updating": "b"]
let overrideHeaders = ["updating": "c", "last-event-id": "eventId2"]
config.method = "REPORT"
config.body = testBody
config.lastEventId = "eventId"
config.headers = testHeaders
config.idleTimeout = 180.0
config.headerTransform = { provided in
XCTAssertEqual(provided, ["removing": "a", "updating": "b", "Last-Event-Id": "eventId"])
return overrideHeaders
}
request = EventSourceDelegate(config: config).createRequest()
XCTAssertEqual(request.url, config.url)
XCTAssertEqual(request.httpMethod, config.method)
XCTAssertEqual(request.httpBody, config.body)
XCTAssertEqual(request.timeoutInterval, config.idleTimeout)
XCTAssertEqual(request.allHTTPHeaderFields, overrideHeaders)
}
func testDispatchError() {
var connectionErrorHandlerCallCount = 0
var connectionErrorAction: ConnectionErrorAction = .proceed
var config = EventSource.Config(handler: mockHandler, url: URL(string: "abc")!)
config.connectionErrorHandler = { _ in
connectionErrorHandlerCallCount += 1
return connectionErrorAction
}
let es = EventSourceDelegate(config: config)
XCTAssertEqual(es.dispatchError(error: DummyError()), .proceed)
XCTAssertEqual(connectionErrorHandlerCallCount, 1)
guard case .error(let err) = mockHandler.events.expectEvent(), err is DummyError
else {
XCTFail("handler should receive error if EventSource is not shutting down")
return
}
mockHandler.events.expectNoEvent()
connectionErrorAction = .shutdown
XCTAssertEqual(es.dispatchError(error: DummyError()), .shutdown)
XCTAssertEqual(connectionErrorHandlerCallCount, 2)
}
func sessionWithMockProtocol() -> URLSessionConfiguration {
let sessionConfig = URLSessionConfiguration.default
sessionConfig.protocolClasses = [MockingProtocol.self] + (sessionConfig.protocolClasses ?? [])
return sessionConfig
}
#if !os(Linux) && !os(Windows)
func testStartDefaultRequest() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
let es = EventSource(config: config)
es.start()
let handler = MockingProtocol.requested.expectEvent()
XCTAssertEqual(handler.request.url, config.url)
XCTAssertEqual(handler.request.httpMethod, config.method)
XCTAssertEqual(handler.request.httpBody, config.body)
XCTAssertEqual(handler.request.timeoutInterval, config.idleTimeout)
XCTAssertEqual(handler.request.allHTTPHeaderFields?["Accept"], "text/event-stream")
XCTAssertEqual(handler.request.allHTTPHeaderFields?["Cache-Control"], "no-cache")
XCTAssertNil(handler.request.allHTTPHeaderFields?["Last-Event-Id"])
es.stop()
}
func testStartRequestWithConfiguration() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
config.method = "REPORT"
config.body = Data("test body".utf8)
config.idleTimeout = 500.0
config.lastEventId = "abc"
config.headers = ["X-LD-Header": "def"]
let es = EventSource(config: config)
es.start()
let handler = MockingProtocol.requested.expectEvent()
XCTAssertEqual(handler.request.url, config.url)
XCTAssertEqual(handler.request.httpMethod, config.method)
XCTAssertEqual(handler.request.bodyStreamAsData(), config.body)
XCTAssertEqual(handler.request.timeoutInterval, config.idleTimeout)
XCTAssertEqual(handler.request.allHTTPHeaderFields?["Accept"], "text/event-stream")
XCTAssertEqual(handler.request.allHTTPHeaderFields?["Cache-Control"], "no-cache")
XCTAssertEqual(handler.request.allHTTPHeaderFields?["Last-Event-Id"], config.lastEventId)
XCTAssertEqual(handler.request.allHTTPHeaderFields?["X-LD-Header"], "def")
es.stop()
}
func testStartRequestIsNotReentrant() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
let es = EventSource(config: config)
es.start()
es.start()
_ = MockingProtocol.requested.expectEvent()
MockingProtocol.requested.expectNoEvent()
es.stop()
}
func testSuccessfulResponseOpens() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
let es = EventSource(config: config)
es.start()
let handler = MockingProtocol.requested.expectEvent()
handler.respond(statusCode: 200)
XCTAssertEqual(mockHandler.events.expectEvent(), .opened)
es.stop()
XCTAssertEqual(mockHandler.events.expectEvent(), .closed)
}
func testLastEventIdUpdatedByEvents() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
config.reconnectTime = 0.1
let es = EventSource(config: config)
es.start()
let handler = MockingProtocol.requested.expectEvent()
handler.respond(statusCode: 200)
XCTAssertEqual(mockHandler.events.expectEvent(), .opened)
XCTAssertEqual(es.getLastEventId(), "")
handler.respond(didLoad: "id: abc\n\n")
// Comment used for synchronization
handler.respond(didLoad: ":comment\n")
XCTAssertEqual(mockHandler.events.expectEvent(), .comment("comment"))
XCTAssertEqual(es.getLastEventId(), "abc")
handler.finish()
XCTAssertEqual(mockHandler.events.expectEvent(), .closed)
// Expect to reconnect and include new event id
let reconnectHandler = MockingProtocol.requested.expectEvent()
XCTAssertEqual(reconnectHandler.request.allHTTPHeaderFields?["Last-Event-Id"], "abc")
es.stop()
}
func testUsesRetryTime() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
// Long enough to cause a timeout if the retry time is not updated
config.reconnectTime = 5
let es = EventSource(config: config)
es.start()
let handler = MockingProtocol.requested.expectEvent()
handler.respond(statusCode: 200)
XCTAssertEqual(mockHandler.events.expectEvent(), .opened)
handler.respond(didLoad: "retry: 100\n\n")
handler.finish()
XCTAssertEqual(mockHandler.events.expectEvent(), .closed)
// Expect to reconnect before this times out
_ = MockingProtocol.requested.expectEvent()
es.stop()
}
func testCallsHandlerWithMessage() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
let es = EventSource(config: config)
es.start()
let handler = MockingProtocol.requested.expectEvent()
handler.respond(statusCode: 200)
XCTAssertEqual(mockHandler.events.expectEvent(), .opened)
handler.respond(didLoad: "event: custom\ndata: {}\n\n")
XCTAssertEqual(mockHandler.events.expectEvent(), .message("custom", MessageEvent(data: "{}")))
es.stop()
XCTAssertEqual(mockHandler.events.expectEvent(), .closed)
}
func testRetryOnInvalidResponseCode() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
config.reconnectTime = 0.1
let es = EventSource(config: config)
es.start()
let handler = MockingProtocol.requested.expectEvent()
handler.respond(statusCode: 400)
guard case let .error(err) = mockHandler.events.expectEvent(),
let responseErr = err as? UnsuccessfulResponseError
else {
XCTFail("Expected UnsuccessfulResponseError to be given to handler")
return
}
XCTAssertEqual(responseErr.responseCode, 400)
// Expect the client to reconnect
_ = MockingProtocol.requested.expectEvent()
es.stop()
}
func testShutdownByErrorHandlerOnInitialErrorResponse() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
config.reconnectTime = 0.1
config.connectionErrorHandler = { err in
if let responseErr = err as? UnsuccessfulResponseError {
XCTAssertEqual(responseErr.responseCode, 400)
} else {
XCTFail("Expected UnsuccessfulResponseError to be given to handler")
}
return .shutdown
}
let es = EventSource(config: config)
es.start()
let handler = MockingProtocol.requested.expectEvent()
handler.respond(statusCode: 400)
// Expect the client not to reconnect
MockingProtocol.requested.expectNoEvent(within: 1.0)
es.stop()
// Error should not have been given to the handler
mockHandler.events.expectNoEvent()
}
func testShutdownByErrorHandlerOnResponseCompletionError() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
config.reconnectTime = 0.1
config.connectionErrorHandler = { _ in
.shutdown
}
let es = EventSource(config: config)
es.start()
let handler = MockingProtocol.requested.expectEvent()
handler.respond(statusCode: 200)
XCTAssertEqual(mockHandler.events.expectEvent(), .opened)
handler.finishWith(error: DummyError())
XCTAssertEqual(mockHandler.events.expectEvent(), .closed)
// Expect the client not to reconnect
MockingProtocol.requested.expectNoEvent(within: 1.0)
es.stop()
// Error should not have been given to the handler
mockHandler.events.expectNoEvent()
}
func testShutdownBy204Response() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
config.reconnectTime = 0.1
let es = EventSource(config: config)
es.start()
let handler = MockingProtocol.requested.expectEvent()
handler.respond(statusCode: 204)
MockingProtocol.requested.expectNoEvent(within: 1.0)
es.stop()
// Error should not have been given to the handler
mockHandler.events.expectNoEvent()
}
func testCanOverride204DefaultBehavior() {
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
config.urlSessionConfiguration = sessionWithMockProtocol()
config.reconnectTime = 0.1
config.connectionErrorHandler = { err in
if let responseErr = err as? UnsuccessfulResponseError {
XCTAssertEqual(responseErr.responseCode, 204)
} else {
XCTFail("Expected UnsuccessfulResponseError to be given to handler")
}
return .shutdown
}
let es = EventSource(config: config)
es.start()
let handler = MockingProtocol.requested.expectEvent()
handler.respond(statusCode: 204)
// Expect the client not to reconnect
MockingProtocol.requested.expectNoEvent(within: 1.0)
es.stop()
// Error should not have been given to the handler
mockHandler.events.expectNoEvent()
}
#endif
}
private class DummyError: Error { }
================================================
FILE: Tests/MockHandler.swift
================================================
@testable import LDSwiftEventSource
enum ReceivedEvent: Equatable {
case opened, closed, message(String, MessageEvent), comment(String), error(Error)
static func == (lhs: ReceivedEvent, rhs: ReceivedEvent) -> Bool {
switch (lhs, rhs) {
case (.opened, .opened):
return true
case (.closed, .closed):
return true
case let (.message(typeLhs, eventLhs), .message(typeRhs, eventRhs)):
return typeLhs == typeRhs && eventLhs == eventRhs
case let (.comment(lhs), .comment(rhs)):
return lhs == rhs
case (.error, .error):
return true
default:
return false
}
}
}
class MockHandler: EventHandler {
var events = EventSink<ReceivedEvent>()
func onOpened() { events.record(.opened) }
func onClosed() { events.record(.closed) }
func onMessage(eventType: String, messageEvent: MessageEvent) { events.record(.message(eventType, messageEvent)) }
func onComment(comment: String) { events.record(.comment(comment)) }
func onError(error: Error) { events.record(.error(error)) }
}
================================================
FILE: Tests/TestUtil.swift
================================================
import XCTest
#if os(Linux) || os(Windows)
import FoundationNetworking
#endif
struct EventSink<T> {
private let semaphore = DispatchSemaphore(value: 0)
private let queue = DispatchQueue(label: "EventSinkQueue." + UUID().uuidString)
var receivedEvents: [T] = []
mutating func record(_ event: T) {
queue.sync { receivedEvents.append(event) }
semaphore.signal()
}
mutating func expectEvent(maxWait: TimeInterval = 1.0) -> T {
switch semaphore.wait(timeout: DispatchTime.now() + maxWait) {
case .success:
return queue.sync { receivedEvents.remove(at: 0) }
case .timedOut:
XCTFail("Expected mock handler to be called")
return (nil as T?)!
}
}
mutating func maybeEvent() -> T? {
switch semaphore.wait(timeout: DispatchTime.now()) {
case .success:
return queue.sync { receivedEvents.remove(at: 0) }
case .timedOut:
return nil
}
}
func expectNoEvent(within: TimeInterval = 0.1) {
if case .success = semaphore.wait(timeout: DispatchTime.now() + within) {
XCTFail("Expected no events in sink, found \(String(describing: receivedEvents.first))")
}
}
}
class RequestHandler {
let proto: URLProtocol
let request: URLRequest
let client: URLProtocolClient?
var stopped = false
init(proto: URLProtocol, request: URLRequest, client: URLProtocolClient?) {
self.proto = proto
self.request = request
self.client = client
}
func respond(statusCode: Int) {
let headers = ["Content-Type": "text/event-stream; charset=utf-8", "Transfer-Encoding": "chunked"]
let resp = HTTPURLResponse(url: request.url!, statusCode: statusCode, httpVersion: nil, headerFields: headers)!
client?.urlProtocol(proto, didReceive: resp, cacheStoragePolicy: .notAllowed)
}
func respond(didLoad: String) {
respond(didLoad: Data(didLoad.utf8))
}
func respond(didLoad: Data) {
client?.urlProtocol(proto, didLoad: didLoad)
}
func finishWith(error: Error) {
client?.urlProtocol(proto, didFailWithError: error)
}
func finish() {
client?.urlProtocolDidFinishLoading(proto)
}
func stop() {
stopped = true
}
}
class MockingProtocol: URLProtocol {
override class func canonicalRequest(for request: URLRequest) -> URLRequest { request }
override class func canInit(with request: URLRequest) -> Bool { true }
override class func canInit(with task: URLSessionTask) -> Bool { true }
static var requested = EventSink<RequestHandler>()
class func resetRequested() {
requested = EventSink<RequestHandler>()
}
private var currentlyLoading: RequestHandler?
override func startLoading() {
let handler = RequestHandler(proto: self, request: request, client: client)
currentlyLoading = handler
MockingProtocol.requested.record(handler)
}
override func stopLoading() {
currentlyLoading?.stop()
currentlyLoading = nil
}
}
extension URLRequest {
func bodyStreamAsData() -> Data? {
guard let bodyStream = self.httpBodyStream
else { return nil }
bodyStream.open()
defer { bodyStream.close() }
let bufSize: Int = 16
let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: bufSize)
defer { buf.deallocate() }
var data = Data()
while bodyStream.hasBytesAvailable {
let readDat = bodyStream.read(buf, maxLength: bufSize)
data.append(buf, count: readDat)
}
return data
}
}
================================================
FILE: Tests/UTF8LineParserTests.swift
================================================
import XCTest
@testable import LDSwiftEventSource
final class UTF8LineParserTests: XCTestCase {
var parser = UTF8LineParser()
override func setUp() {
super.setUp()
parser = UTF8LineParser()
}
override func tearDown() {
super.tearDown()
// Validate that `closeAndReset` completely resets the parser
parser.closeAndReset()
XCTAssertEqual(parser.append(Data("\n".utf8)), [""])
}
// swiftlint:disable:next empty_xctest_method - Only runs test in tearDown
func testNoData() { }
func testEmptyData() {
XCTAssertEqual(parser.append(Data()), [])
}
func testEmptyCrLine() {
XCTAssertEqual(parser.append(Data("\r".utf8)), [""])
}
func testBasicLineUnterminated() {
let line = "test string"
XCTAssertEqual(parser.append(Data(line.utf8)), [])
}
func testBasicLineCr() {
let line = "test string"
let data = Data((line + "\r").utf8)
XCTAssertEqual(parser.append(data), [line])
}
func testBasicLineLf() {
let line = "test string"
let data = Data((line + "\n").utf8)
XCTAssertEqual(parser.append(data), [line])
}
func testBasicLineCrLf() {
let line = "test string"
let data = Data((line + "\r\n").utf8)
XCTAssertEqual(parser.append(data), [line])
}
func testBasicSplit() {
XCTAssertEqual(parser.append(Data("test ".utf8)), [])
XCTAssertEqual(parser.append(Data("string\r".utf8)), ["test string"])
}
func testUnicodeString() {
let line = "¯\\_(ツ)_/¯0️⃣🇺🇸Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮𝓯𝓸𝔁"
XCTAssertEqual(parser.append(Data((line + "\n").utf8)), [line])
}
func testNullCodePoint() {
let line = "\u{0000}"
XCTAssertEqual(parser.append(Data((line + "\n").utf8)), [line])
}
func testInvalidCharacterReplaced() {
let line = "test✨string"
var data = Data((line + "\n").utf8)
// Remove 3rd and last byte of "✨"
data.remove(at: 6)
let expected = "test�string"
XCTAssertEqual(parser.append(data), [expected])
}
// Simulates a multi-code-unit code point being split across received chunks from the network.
func testCodePointSplitNotReplaced() {
let line = "test✨string"
let data = Data((line + "\r").utf8)
let data1 = data.subdata(in: 0..<6)
let data2 = data.subdata(in: 6..<14)
XCTAssertEqual(parser.append(data1), [])
XCTAssertEqual(parser.append(data2), [line])
}
// Simulates the stream dropping part way through a multi-code-unit code point.
func testResetAfterPartialInvalid() {
var data = Data("test✨".utf8)
data.remove(at: 6)
XCTAssertEqual(parser.append(data), [])
}
func testInvalidCharacterReplacedOnNextLineAfterCr() {
let line = "test\r✨string\r"
var data = Data(line.utf8)
// Remove 3rd and last byte of "✨"
data.remove(at: 7)
XCTAssertEqual(parser.append(data), ["test", "�string"])
}
func testMultiLineDataMixedLineEnding() {
let line = "test1\rtest2\ntest3\r\ntest4\r\rtest5\n\n"
let data = Data(line.utf8)
let expected = ["test1", "test2", "test3", "test4", "", "test5", ""]
XCTAssertEqual(parser.append(data), expected)
}
}
================================================
FILE: release-please-config.json
================================================
{
"packages": {
".": {
"release-type": "simple",
"bump-minor-pre-major": true,
"versioning": "default",
"include-v-in-tag": false,
"include-component-in-tag": false,
"extra-files": [
"LDSwiftEventSource.podspec",
"README.md"
]
}
}
}
gitextract_1la2044u/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── actions/ │ │ ├── build-docs/ │ │ │ └── action.yml │ │ ├── build-ios/ │ │ │ └── action.yml │ │ ├── build-macos/ │ │ │ └── action.yml │ │ ├── build-tvos/ │ │ │ └── action.yml │ │ ├── build-watchos/ │ │ │ └── action.yml │ │ ├── contract-tests/ │ │ │ └── action.yml │ │ ├── lint/ │ │ │ └── action.yml │ │ ├── publish/ │ │ │ └── action.yml │ │ ├── publish-docs/ │ │ │ └── action.yml │ │ ├── test-swiftpm/ │ │ │ └── action.yml │ │ └── update-versions/ │ │ └── action.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── ci.yml │ ├── lint-pr-title.yml │ ├── manual-publish-docs.yml │ ├── manual-publish.yml │ ├── release-please.yml │ └── stale.yml ├── .gitignore ├── .jazzy.yaml ├── .release-please-manifest.json ├── .swiftlint.yml ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── ContractTestService/ │ ├── .gitignore │ ├── Package.resolved │ ├── Package.swift │ ├── README.md │ └── Sources/ │ └── ContractTestService/ │ └── main.swift ├── LDSwiftEventSource.podspec ├── LDSwiftEventSource.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ └── contents.xcworkspacedata │ └── xcshareddata/ │ └── xcschemes/ │ └── LDSwiftEventSource.xcscheme ├── LICENSE.txt ├── Makefile ├── Package.swift ├── README.md ├── SECURITY.md ├── Source/ │ ├── .swiftlint.yml │ ├── EventParser.swift │ ├── Info.plist │ ├── LDSwiftEventSource.h │ ├── LDSwiftEventSource.swift │ ├── Logs.swift │ ├── Types.swift │ └── UTF8LineParser.swift ├── Tests/ │ ├── .swiftlint.yml │ ├── EventParserTests.swift │ ├── LDSwiftEventSourceTests.swift │ ├── MockHandler.swift │ ├── TestUtil.swift │ └── UTF8LineParserTests.swift └── release-please-config.json
Condensed preview — 56 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (150K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 646,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 606,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
},
{
"path": ".github/actions/build-docs/action.yml",
"chars": 413,
"preview": "name: Build Documentation\ndescription: 'Build Documentation.'\n\nruns:\n using: composite\n steps:\n - name: Install jaz"
},
{
"path": ".github/actions/build-ios/action.yml",
"chars": 927,
"preview": "name: Build & Test iOS\ndescription: 'Build for iOS device and run tests on iOS Simulator.'\ninputs:\n ios-sim:\n descri"
},
{
"path": ".github/actions/build-macos/action.yml",
"chars": 408,
"preview": "name: Build & Test macOS\ndescription: 'Build and test for macOS.'\n\nruns:\n using: composite\n steps:\n - name: Build &"
},
{
"path": ".github/actions/build-tvos/action.yml",
"chars": 841,
"preview": "name: Build & Test tvOS\ndescription: 'Build for tvOS device and run tests on tvOS Simulator.'\n\nruns:\n using: composite\n"
},
{
"path": ".github/actions/build-watchos/action.yml",
"chars": 731,
"preview": "name: Build watchOS\ndescription: 'Build for watchOS device and simulator.'\n\nruns:\n using: composite\n steps:\n # Work"
},
{
"path": ".github/actions/contract-tests/action.yml",
"chars": 851,
"preview": "name: Contract Tests\ndescription: 'Build and run SDK contract tests.'\ninputs:\n token:\n description: 'GH token used t"
},
{
"path": ".github/actions/lint/action.yml",
"chars": 542,
"preview": "name: Lint\ndescription: 'Run podspec and swiftlint checks.'\n\nruns:\n using: composite\n steps:\n - name: Install swift"
},
{
"path": ".github/actions/publish/action.yml",
"chars": 388,
"preview": "name: Publish Package\ndescription: 'Publish the package to Cocoapods'\ninputs:\n dry_run:\n description: 'Is this a dry"
},
{
"path": ".github/actions/publish-docs/action.yml",
"chars": 399,
"preview": "name: Publish Documentation\ndescription: 'Publish the documentation to GitHub pages'\ninputs:\n token:\n description: '"
},
{
"path": ".github/actions/test-swiftpm/action.yml",
"chars": 210,
"preview": "name: Test SwiftPM\ndescription: 'Build and test using Swift Package Manager.'\n\nruns:\n using: composite\n steps:\n - n"
},
{
"path": ".github/actions/update-versions/action.yml",
"chars": 2570,
"preview": "name: Update xcode project version numbers\ndescription: 'Update xcode project version numbers'\ninputs:\n branch:\n des"
},
{
"path": ".github/pull_request_template.md",
"chars": 737,
"preview": "**Requirements**\n\n- [ ] I have added test coverage for new or changed functionality\n- [ ] I have followed the repository"
},
{
"path": ".github/workflows/ci.yml",
"chars": 3068,
"preview": "name: Run CI\non:\n push:\n branches: [ main ]\n paths-ignore:\n - '**.md' # Do not need to run CI for markdown c"
},
{
"path": ".github/workflows/lint-pr-title.yml",
"chars": 208,
"preview": "name: Lint PR title\n\non:\n pull_request_target:\n types:\n - opened\n - edited\n - synchronize\n\njobs:\n li"
},
{
"path": ".github/workflows/manual-publish-docs.yml",
"chars": 1017,
"preview": "on:\n workflow_dispatch:\n\nname: Publish Documentation\njobs:\n build-publish:\n runs-on: macos-15\n\n permissions:\n "
},
{
"path": ".github/workflows/manual-publish.yml",
"chars": 1351,
"preview": "name: Publish Package\non:\n workflow_dispatch:\n inputs:\n dry_run:\n description: 'Is this a dry run. If so"
},
{
"path": ".github/workflows/release-please.yml",
"chars": 2991,
"preview": "name: Run Release Please\n\non:\n push:\n branches:\n - main\n\njobs:\n release-package:\n runs-on: macos-15\n\n pe"
},
{
"path": ".github/workflows/stale.yml",
"chars": 284,
"preview": "name: \"Close stale issues and PRs\"\non:\n workflow_dispatch:\n schedule:\n # Happen once per day at 1:30 AM\n - cron:"
},
{
"path": ".gitignore",
"chars": 81,
"preview": "*~\n\\#*\n.\\#*\n.DS_Store\n/.build\nxcuserdata/\nIDEWorkspaceChecks.plist\n.swiftpm\n/docs"
},
{
"path": ".jazzy.yaml",
"chars": 314,
"preview": "module: LDSwiftEventSource\nauthor: LaunchDarkly\nauthor_url: https://launchdarkly.com\ngithub_url: https://github.com/laun"
},
{
"path": ".release-please-manifest.json",
"chars": 19,
"preview": "{\n \".\": \"3.3.0\"\n}\n"
},
{
"path": ".swiftlint.yml",
"chars": 2241,
"preview": "# See sub-configurations at `Source/.swiftlint.yml` and `Tests/.swiftlint.yml`.\n\ndisabled_rules:\n - identifier_name\n -"
},
{
"path": "CHANGELOG.md",
"chars": 6568,
"preview": "# Change log\n\nAll notable changes to the LaunchDarkly Swift EventSource library will be documented in this file. This pr"
},
{
"path": "CODEOWNERS",
"chars": 56,
"preview": "# Repository Maintainers\n* @launchdarkly/team-sdk-swift\n"
},
{
"path": "CONTRIBUTING.md",
"chars": 2123,
"preview": "Contributing to the LDSwiftEventSource library\n================================================\n\nSubmitting bug reports "
},
{
"path": "ContractTestService/.gitignore",
"chars": 126,
"preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/xcode/package.xcworkspace/contents.xcworkspac"
},
{
"path": "ContractTestService/Package.resolved",
"chars": 2511,
"preview": "{\n \"object\": {\n \"pins\": [\n {\n \"package\": \"Socket\",\n \"repositoryURL\": \"https://github.com/Kitura/B"
},
{
"path": "ContractTestService/Package.swift",
"chars": 636,
"preview": "// swift-tools-version:5.0\n\nimport PackageDescription\n\nlet package = Package(\n name: \"ContractTestService\",\n platforms"
},
{
"path": "ContractTestService/README.md",
"chars": 589,
"preview": "# SSE client contract test service\n\nThis directory contains an implementation of the cross-platform SSE testing protocol"
},
{
"path": "ContractTestService/Sources/ContractTestService/main.swift",
"chars": 3965,
"preview": "import Dispatch\nimport Foundation\nimport Kitura\nimport LDSwiftEventSource\n\nstruct StatusResp: Encodable {\n let name ="
},
{
"path": "LDSwiftEventSource.podspec",
"chars": 733,
"preview": "Pod::Spec.new do |s|\n s.name = \"LDSwiftEventSource\"\n s.version = \"3.3.0\" # x-release-please-version\n s.s"
},
{
"path": "LDSwiftEventSource.xcodeproj/project.pbxproj",
"chars": 24497,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "LDSwiftEventSource.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 163,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:LDSwiftEventSou"
},
{
"path": "LDSwiftEventSource.xcodeproj/xcshareddata/xcschemes/LDSwiftEventSource.xcscheme",
"chars": 3387,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1140\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "LICENSE.txt",
"chars": 557,
"preview": "Copyright 2020 Catamorphic, Co.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this fi"
},
{
"path": "Makefile",
"chars": 925,
"preview": "build:\n\tswift build\n\nclean:\n\tswift clean\n\ntest:\n\tswift test\n\nTEMP_TEST_OUTPUT=/tmp/sse-contract-test-service.log\n\nbuild-"
},
{
"path": "Package.swift",
"chars": 632,
"preview": "// swift-tools-version:5.0\n\nimport PackageDescription\n\nlet package = Package(\n name: \"LDSwiftEventSource\",\n platfo"
},
{
"path": "README.md",
"chars": 4749,
"preview": "# LDSwiftEventSource\n\n[]("
},
{
"path": "SECURITY.md",
"chars": 554,
"preview": "# Reporting and Fixing Security Issues\n\nPlease report all security issues to the LaunchDarkly security team by submittin"
},
{
"path": "Source/.swiftlint.yml",
"chars": 86,
"preview": "disabled_rules:\n\nopt_in_rules:\n - force_unwrapping\n - implicitly_unwrapped_optional\n"
},
{
"path": "Source/EventParser.swift",
"chars": 3218,
"preview": "import Foundation\n\nclass EventParser {\n private struct Constants {\n static let dataLabel: Substring = \"data\"\n "
},
{
"path": "Source/Info.plist",
"chars": 769,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Source/LDSwiftEventSource.h",
"chars": 152,
"preview": "@import Foundation;\n\nFOUNDATION_EXPORT double LDSwiftEventSourceVersionNumber;\nFOUNDATION_EXPORT const unsigned char LDS"
},
{
"path": "Source/LDSwiftEventSource.swift",
"chars": 13510,
"preview": "import Foundation\n\n#if os(Linux) || os(Windows)\nimport FoundationNetworking\n#endif\n\n#if canImport(os)\n// os_log is not s"
},
{
"path": "Source/Logs.swift",
"chars": 1603,
"preview": "import Foundation\n\n#if canImport(os)\nimport os.log\n#endif\n\nprotocol InternalLogging {\n func log(_ level: Level, _ sta"
},
{
"path": "Source/Types.swift",
"chars": 4017,
"preview": "import Foundation\n\n/**\n Type for a function that will be notified when the `EventSource` client encounters a connection "
},
{
"path": "Source/UTF8LineParser.swift",
"chars": 2197,
"preview": "import Foundation\n\nstruct DataIter: IteratorProtocol {\n var data: Data\n var position: Data.Index { data.startIndex"
},
{
"path": "Tests/.swiftlint.yml",
"chars": 373,
"preview": "disabled_rules:\n\nopt_in_rules:\n # Must specify even though enabled by default to update configuration of rule below.\n "
},
{
"path": "Tests/EventParserTests.swift",
"chars": 11889,
"preview": "import XCTest\n@testable import LDSwiftEventSource\n\nfinal class EventParserTests: XCTestCase {\n var handler: MockHandl"
},
{
"path": "Tests/LDSwiftEventSourceTests.swift",
"chars": 18082,
"preview": "import XCTest\n@testable import LDSwiftEventSource\n\n#if os(Linux) || os(Windows)\nimport FoundationNetworking\n#endif\n\nfina"
},
{
"path": "Tests/MockHandler.swift",
"chars": 1133,
"preview": "@testable import LDSwiftEventSource\n\nenum ReceivedEvent: Equatable {\n case opened, closed, message(String, MessageEve"
},
{
"path": "Tests/TestUtil.swift",
"chars": 3705,
"preview": "import XCTest\n\n#if os(Linux) || os(Windows)\nimport FoundationNetworking\n#endif\n\nstruct EventSink<T> {\n private let se"
},
{
"path": "Tests/UTF8LineParserTests.swift",
"chars": 3371,
"preview": "import XCTest\n@testable import LDSwiftEventSource\n\nfinal class UTF8LineParserTests: XCTestCase {\n var parser = UTF8Li"
},
{
"path": "release-please-config.json",
"chars": 303,
"preview": "{\n \"packages\": {\n \".\": {\n \"release-type\": \"simple\",\n \"bump-minor-pre-major\": true,\n \"versioning\": \"de"
}
]
About this extraction
This page contains the full source code of the launchdarkly/swift-eventsource GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 56 files (135.8 KB), approximately 36.9k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.