Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ac2ca73ec | |||
| 7b67a2f758 | |||
| 8b86145519 | |||
| ac7a8dd90e | |||
| 6903b7f768 | |||
| 2e8b8d7330 | |||
| b4d5375954 | |||
| 3f3f90103c | |||
| 8582c12ec7 | |||
| 98b3798264 | |||
| 6332a9cef9 | |||
| efcce32b50 | |||
| 0311a28571 | |||
| cb001f7e91 | |||
| 9ab2b304b8 | |||
| 1fd4dd735d | |||
| 99e9226338 | |||
| 0c12f52569 | |||
| a6f01ba71e | |||
| 77526b1fae | |||
| 7c97445155 | |||
| a0f1a4ce80 | |||
| 80fbbcddf3 | |||
| 0ab87e25a4 | |||
| 495255206e | |||
| 46254730bd | |||
| d1e60babf4 | |||
| ab84f685c0 | |||
| 1a8c4bfebc | |||
| b15b48f702 | |||
| 29c732faa0 | |||
| dcebccf947 | |||
| 67138c7035 | |||
| 6f848be1ee | |||
| b6d02dbe78 | |||
| ffecf780ee | |||
| fad9d2d26f |
28
.gitea/tools/render-docker-compose-ci.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
set +x
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
cat >"./docker-compose-ci-${COMPOSE_PROJECT}.yaml" <<EOF
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/postgres:15.13-${COMPOSE_PROJECT}"
|
||||||
|
|
||||||
|
keycloak:
|
||||||
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/keycloak:22.0.3-${COMPOSE_PROJECT}"
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/rabbitmq:3.10.8-${COMPOSE_PROJECT}"
|
||||||
|
|
||||||
|
apple-ci:
|
||||||
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/apple:ci-${COMPOSE_PROJECT}"
|
||||||
|
|
||||||
|
backend-ci:
|
||||||
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:ci-${COMPOSE_PROJECT}"
|
||||||
|
|
||||||
|
extension-ci:
|
||||||
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-${COMPOSE_PROJECT}"
|
||||||
|
|
||||||
|
packages-ci:
|
||||||
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/packages:ci-${COMPOSE_PROJECT}"
|
||||||
|
EOF
|
||||||
@@ -2,7 +2,13 @@ name: "CI"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "development"
|
||||||
|
- "public"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "development"
|
||||||
|
- "public"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-checks:
|
run-checks:
|
||||||
@@ -11,8 +17,35 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: "Checkout the code"
|
- name: "Checkout the code"
|
||||||
uses: "actions/checkout@v2"
|
uses: "actions/checkout@v2"
|
||||||
|
- name: "Get run info"
|
||||||
|
id: "get-run-info"
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
echo "COMPOSE_PROJECT=${{ vars.COMPOSE_PROJECT_BASE }}-${GITHUB_RUN_NUMBER}" >> $GITHUB_OUTPUT
|
||||||
|
- name: "Get build options"
|
||||||
|
id: "get-build-options"
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
SHORT_SHA="${GITHUB_SHA::8}"
|
||||||
|
BUILD_ARCH="amd64"
|
||||||
|
BUILD_PLATFORM="linux/amd64"
|
||||||
|
if [ "${RUNNER_ARCH}" = "ARM64" ];then
|
||||||
|
BUILD_ARCH="arm64"
|
||||||
|
BUILD_PLATFORM="linux/arm64"
|
||||||
|
fi
|
||||||
|
echo "SHORT_SHA=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||||
|
echo "BUILD_ARCH=$BUILD_ARCH" >> $GITHUB_OUTPUT
|
||||||
|
echo "BUILD_PLATFORM=$BUILD_PLATFORM" >> $GITHUB_OUTPUT
|
||||||
- name: "Set up Docker Buildx"
|
- name: "Set up Docker Buildx"
|
||||||
|
id: "setup-docker-buildx"
|
||||||
uses: "docker/setup-buildx-action@v3"
|
uses: "docker/setup-buildx-action@v3"
|
||||||
|
with:
|
||||||
|
driver: "remote"
|
||||||
|
endpoint: "tcp://builder-01.bthlab:2375"
|
||||||
|
platforms: "linux/amd64"
|
||||||
|
append: |
|
||||||
|
- endpoint: "tcp://builder-mac-01.bthlab:2375"
|
||||||
|
platforms: "linux/arm64"
|
||||||
- name: "Build `postgres` image"
|
- name: "Build `postgres` image"
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
@@ -20,7 +53,10 @@ jobs:
|
|||||||
context: "services/"
|
context: "services/"
|
||||||
push: false
|
push: false
|
||||||
load: true
|
load: true
|
||||||
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/postgres:15.13-local"
|
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/postgres:15.13-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
|
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
|
||||||
|
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
|
||||||
|
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
|
||||||
- name: "Build `keycloak` image"
|
- name: "Build `keycloak` image"
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
@@ -28,7 +64,10 @@ jobs:
|
|||||||
context: "services/"
|
context: "services/"
|
||||||
push: false
|
push: false
|
||||||
load: true
|
load: true
|
||||||
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/keycloak:22.0.3-local"
|
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/keycloak:22.0.3-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
|
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
|
||||||
|
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
|
||||||
|
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
|
||||||
- name: "Build `rabbitmq` image"
|
- name: "Build `rabbitmq` image"
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
@@ -36,7 +75,10 @@ jobs:
|
|||||||
context: "services/"
|
context: "services/"
|
||||||
push: false
|
push: false
|
||||||
load: true
|
load: true
|
||||||
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/rabbitmq:3.10.8-local"
|
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/rabbitmq:3.10.8-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
|
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
|
||||||
|
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
|
||||||
|
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
|
||||||
- name: "Build `backend-ci` image"
|
- name: "Build `backend-ci` image"
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
@@ -45,7 +87,10 @@ jobs:
|
|||||||
target: "ci"
|
target: "ci"
|
||||||
push: false
|
push: false
|
||||||
load: true
|
load: true
|
||||||
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:ci-local"
|
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:ci-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
|
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
|
||||||
|
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
|
||||||
|
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
|
||||||
- name: "Build `packages-ci` image"
|
- name: "Build `packages-ci` image"
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
@@ -54,17 +99,103 @@ jobs:
|
|||||||
target: "ci"
|
target: "ci"
|
||||||
push: false
|
push: false
|
||||||
load: true
|
load: true
|
||||||
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/packages:ci-local"
|
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/packages:ci-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
|
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
|
||||||
|
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
|
||||||
|
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
|
||||||
|
- name: "Build `extension-ci` image"
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
file: "services/extension/Dockerfile"
|
||||||
|
context: "services/"
|
||||||
|
target: "ci"
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
|
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
|
||||||
|
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
|
||||||
|
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
|
||||||
|
- name: "Build `apple-ci` image"
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
file: "services/apple/Dockerfile"
|
||||||
|
context: "services/"
|
||||||
|
target: "ci"
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/apple:ci-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
|
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
|
||||||
|
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
|
||||||
|
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
|
||||||
|
- name: "Prepare the build"
|
||||||
|
id: "prepare"
|
||||||
|
env:
|
||||||
|
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
./.gitea/tools/render-docker-compose-ci.sh
|
||||||
- name: "Run `backend` checks"
|
- name: "Run `backend` checks"
|
||||||
|
if: "steps.prepare.conclusion == 'success'"
|
||||||
|
env:
|
||||||
|
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm backend-ci inv ci
|
docker compose \
|
||||||
|
-p "${COMPOSE_PROJECT}" \
|
||||||
|
-f "docker-compose.yaml" \
|
||||||
|
-f "docker-compose-ci.yaml" \
|
||||||
|
-f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" \
|
||||||
|
run --rm \
|
||||||
|
backend-ci inv ci
|
||||||
- name: "Run `packages` checks"
|
- name: "Run `packages` checks"
|
||||||
|
if: "steps.prepare.conclusion == 'success'"
|
||||||
|
env:
|
||||||
|
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm packages-ci inv ci
|
docker compose \
|
||||||
|
-p "${COMPOSE_PROJECT}" \
|
||||||
|
-f "docker-compose.yaml" \
|
||||||
|
-f "docker-compose-ci.yaml" \
|
||||||
|
-f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" \
|
||||||
|
run --rm \
|
||||||
|
packages-ci inv ci
|
||||||
|
- name: "Run `extension` checks"
|
||||||
|
if: "steps.prepare.conclusion == 'success'"
|
||||||
|
env:
|
||||||
|
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
docker compose \
|
||||||
|
-p "${COMPOSE_PROJECT}" \
|
||||||
|
-f "docker-compose.yaml" \
|
||||||
|
-f "docker-compose-ci.yaml" \
|
||||||
|
-f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" \
|
||||||
|
run --rm \
|
||||||
|
extension-ci inv ci
|
||||||
|
- name: "Run `apple` checks"
|
||||||
|
if: "steps.prepare.conclusion == 'success'"
|
||||||
|
env:
|
||||||
|
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
docker compose \
|
||||||
|
-p "${COMPOSE_PROJECT}" \
|
||||||
|
-f "docker-compose.yaml" \
|
||||||
|
-f "docker-compose-ci.yaml" \
|
||||||
|
-f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" \
|
||||||
|
run --rm \
|
||||||
|
apple-ci inv ci
|
||||||
- name: "Clean up"
|
- name: "Clean up"
|
||||||
if: always()
|
if: always()
|
||||||
|
env:
|
||||||
|
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml down --volumes
|
docker compose \
|
||||||
|
-p "${COMPOSE_PROJECT}" \
|
||||||
|
-f "docker-compose.yaml" \
|
||||||
|
-f "docker-compose-ci.yaml" \
|
||||||
|
-f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" \
|
||||||
|
down --volumes --rmi all || true
|
||||||
|
rm -f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" || true
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.envrc*
|
.envrc*
|
||||||
.ipythonhome/
|
.ipythonhome/
|
||||||
|
/docker-compose-ci-*.yaml
|
||||||
|
|||||||
@@ -86,3 +86,5 @@ Licensed under terms of the MIT License
|
|||||||
Pepper Hot Solid icon
|
Pepper Hot Solid icon
|
||||||
Copyright (c) Icons8
|
Copyright (c) Icons8
|
||||||
Licensed under terms of the MIT License
|
Licensed under terms of the MIT License
|
||||||
|
|
||||||
|
Spinner Loader CSS from https://css-loaders.com/
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ $ docker run --rm -it \
|
|||||||
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME=hotpocket \
|
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME=hotpocket \
|
||||||
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD=hotpocketm4st3r \
|
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD=hotpocketm4st3r \
|
||||||
-p 8000:8000 \
|
-p 8000:8000 \
|
||||||
docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v1.0.2-01
|
docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.10.13-01
|
||||||
```
|
```
|
||||||
|
|
||||||
The command above will set up and start the application. The SQLite file will
|
The command above will set up and start the application. The SQLite file will
|
||||||
@@ -76,8 +76,7 @@ credentials. The Web app will be reachable at `http://127.0.0.1:8000/`.
|
|||||||
The admin will be reachable at `http://127.0.0.1:8000/admin/`.
|
The admin will be reachable at `http://127.0.0.1:8000/admin/`.
|
||||||
|
|
||||||
The `DJANGO_SETTINGS_MODULE` environment variable defaults to
|
The `DJANGO_SETTINGS_MODULE` environment variable defaults to
|
||||||
`hotpocket_backend.settings.deployment.webapp`. This should be set to
|
`hotpocket_backend.settings.deployment.aio`.
|
||||||
`hotpocket_backend.settings.deployment.admin` in the Admin container.
|
|
||||||
|
|
||||||
**NOTE:** The command above specifies wildly insecure `SECRET_KEY` which is
|
**NOTE:** The command above specifies wildly insecure `SECRET_KEY` which is
|
||||||
used among other things to secure the session cookie. Please *please*
|
used among other things to secure the session cookie. Please *please*
|
||||||
@@ -94,7 +93,8 @@ backend etc. The final deployment will require services for at least the Web
|
|||||||
app, the Celery worker and Celery Beat. Admin is optional.
|
app, the Celery worker and Celery Beat. Admin is optional.
|
||||||
|
|
||||||
The `DJANGO_SETTINGS_MODULE` environment variable defaults to
|
The `DJANGO_SETTINGS_MODULE` environment variable defaults to
|
||||||
`hotpocket_backend.settings.deployment.aio`.
|
`hotpocket_backend.settings.deployment.webapp`. This should be set to
|
||||||
|
`hotpocket_backend.settings.deployment.admin` in the Admin container.
|
||||||
|
|
||||||
The `deployment/fullstack/docker-compose.yaml` file can be used as a
|
The `deployment/fullstack/docker-compose.yaml` file can be used as a
|
||||||
starting point for full-stack deployments.
|
starting point for full-stack deployments.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v1.0.2-01"
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.10.13-01"
|
||||||
environment:
|
environment:
|
||||||
HOTPOCKET_BACKEND_SECRET_KEY: "thisisntright"
|
HOTPOCKET_BACKEND_SECRET_KEY: "thisisntright"
|
||||||
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME: "hotpocket"
|
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME: "hotpocket"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ x-backend-environment: &x-backend-environment
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
webapp:
|
webapp:
|
||||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v1.0.2-01"
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
|
||||||
environment:
|
environment:
|
||||||
<<: *x-backend-environment
|
<<: *x-backend-environment
|
||||||
HOTPOCKET_BACKEND_ALLOWED_HOSTS: "app.staging.hotpocket.bthlab.bthlabs.net"
|
HOTPOCKET_BACKEND_ALLOWED_HOSTS: "app.staging.hotpocket.bthlab.bthlabs.net"
|
||||||
@@ -21,7 +21,7 @@ services:
|
|||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
|
|
||||||
admin:
|
admin:
|
||||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v1.0.2-01"
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
|
||||||
environment:
|
environment:
|
||||||
<<: *x-backend-environment
|
<<: *x-backend-environment
|
||||||
HOTPOCKET_BACKEND_APP: "admin"
|
HOTPOCKET_BACKEND_APP: "admin"
|
||||||
@@ -35,7 +35,7 @@ services:
|
|||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
|
|
||||||
celery-worker:
|
celery-worker:
|
||||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v1.0.2-01"
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
|
||||||
command:
|
command:
|
||||||
- "/srv/venv/bin/celery"
|
- "/srv/venv/bin/celery"
|
||||||
- "-A"
|
- "-A"
|
||||||
@@ -57,7 +57,7 @@ services:
|
|||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
|
|
||||||
celery-beat:
|
celery-beat:
|
||||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v1.0.2-01"
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
|
||||||
command:
|
command:
|
||||||
- "/srv/venv/bin/celery"
|
- "/srv/venv/bin/celery"
|
||||||
- "-A"
|
- "-A"
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
"group": {
|
"group": {
|
||||||
"default": {
|
"default": {
|
||||||
"targets": [
|
"targets": [
|
||||||
|
"apple-management",
|
||||||
"backend-management",
|
"backend-management",
|
||||||
"caddy",
|
"caddy",
|
||||||
|
"extension-management",
|
||||||
"keycloak",
|
"keycloak",
|
||||||
"packages-management",
|
"packages-management",
|
||||||
"postgres",
|
"postgres",
|
||||||
@@ -12,6 +14,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
|
"apple-management": {
|
||||||
|
"context": "services/",
|
||||||
|
"dockerfile": "apple/Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"docker-hosted.nexus.bthlabs.pl/hotpocket/apple:local"
|
||||||
|
],
|
||||||
|
"target": "development",
|
||||||
|
"output": [
|
||||||
|
"type=docker,load=true,push=false"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"apple-ci": {
|
||||||
|
"context": "services/",
|
||||||
|
"dockerfile": "apple/Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"docker-hosted.nexus.bthlabs.pl/hotpocket/apple:ci-local"
|
||||||
|
],
|
||||||
|
"target": "ci",
|
||||||
|
"output": [
|
||||||
|
"type=docker,load=true,push=false"
|
||||||
|
]
|
||||||
|
},
|
||||||
"backend-management": {
|
"backend-management": {
|
||||||
"context": "services/",
|
"context": "services/",
|
||||||
"dockerfile": "backend/Dockerfile",
|
"dockerfile": "backend/Dockerfile",
|
||||||
@@ -67,6 +91,28 @@
|
|||||||
"type=docker,load=true,push=false"
|
"type=docker,load=true,push=false"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"extension-management": {
|
||||||
|
"context": "services/",
|
||||||
|
"dockerfile": "extension/Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"docker-hosted.nexus.bthlabs.pl/hotpocket/extension:local"
|
||||||
|
],
|
||||||
|
"target": "development",
|
||||||
|
"output": [
|
||||||
|
"type=docker,load=true,push=false"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"extension-ci": {
|
||||||
|
"context": "services/",
|
||||||
|
"dockerfile": "extension/Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-local"
|
||||||
|
],
|
||||||
|
"target": "ci",
|
||||||
|
"output": [
|
||||||
|
"type=docker,load=true,push=false"
|
||||||
|
]
|
||||||
|
},
|
||||||
"caddy": {
|
"caddy": {
|
||||||
"context": "services/",
|
"context": "services/",
|
||||||
"dockerfile": "caddy/Dockerfile",
|
"dockerfile": "caddy/Dockerfile",
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
ports: []
|
ports: !override []
|
||||||
|
|
||||||
keycloak:
|
keycloak:
|
||||||
command: "echo 'NOOP'"
|
command: "echo 'NOOP'"
|
||||||
ports: []
|
ports: !override []
|
||||||
restart: "no"
|
restart: "no"
|
||||||
|
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
ports: []
|
ports: !override []
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- path: "./services/backend/docker-compose-ci.yaml"
|
- path: "./services/backend/docker-compose-ci.yaml"
|
||||||
- path: "./services/packages/docker-compose-ci.yaml"
|
- path: "./services/packages/docker-compose-ci.yaml"
|
||||||
|
- path: "./services/extension/docker-compose-ci.yaml"
|
||||||
|
- path: "./services/apple/docker-compose-ci.yaml"
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ include:
|
|||||||
- path: "./docker-compose-cloud.yaml"
|
- path: "./docker-compose-cloud.yaml"
|
||||||
- path: "./services/backend/docker-compose.yaml"
|
- path: "./services/backend/docker-compose.yaml"
|
||||||
- path: "./services/packages/docker-compose.yaml"
|
- path: "./services/packages/docker-compose.yaml"
|
||||||
|
- path: "./services/extension/docker-compose.yaml"
|
||||||
|
- path: "./services/apple/docker-compose.yaml"
|
||||||
|
|
||||||
volumes: {}
|
volumes: {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"run": {
|
|
||||||
"echo": true,
|
|
||||||
"pty": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
8
invoke.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
run:
|
||||||
|
echo: true
|
||||||
|
pty: true
|
||||||
|
files_to_version:
|
||||||
|
- "deployment/aio/docker-compose.yaml"
|
||||||
|
- "deployment/fullstack/docker-compose.yaml"
|
||||||
|
- "pyproject.toml"
|
||||||
|
- "README.md"
|
||||||
3
poetry.lock
generated
@@ -9,6 +9,9 @@ python-versions = "^3.12"
|
|||||||
files = []
|
files = []
|
||||||
develop = true
|
develop = true
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
invoke = "2.2.0"
|
||||||
|
|
||||||
[package.source]
|
[package.source]
|
||||||
type = "directory"
|
type = "directory"
|
||||||
url = "services/packages/workspace_tools"
|
url = "services/packages/workspace_tools"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "hotpocket-workspace"
|
name = "hotpocket-workspace"
|
||||||
version = "1.0.0"
|
version = "25.10.13"
|
||||||
description = "HotPocket Workspace"
|
description = "HotPocket Workspace"
|
||||||
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
|
.mypy_cache/
|
||||||
|
.pytest_cache/
|
||||||
_tmp/
|
_tmp/
|
||||||
|
apple/build/
|
||||||
|
apple/DerivedData/
|
||||||
backend/node_modules/
|
backend/node_modules/
|
||||||
backend/ops/metal/
|
backend/ops/metal/
|
||||||
backend/hotpocket_backend/playground.py
|
backend/hotpocket_backend/playground.py
|
||||||
@@ -7,4 +11,6 @@ backend/hotpocket_backend/settings/docker/
|
|||||||
backend/hotpocket_backend/secrets/metal/
|
backend/hotpocket_backend/secrets/metal/
|
||||||
backend/hotpocket_backend/settings/metal/
|
backend/hotpocket_backend/settings/metal/
|
||||||
backend/hotpocket_backend/static/
|
backend/hotpocket_backend/static/
|
||||||
|
extension/node_modules/
|
||||||
|
extension/dist/
|
||||||
.envrc*
|
.envrc*
|
||||||
|
|||||||
94
services/apple/.gitignore
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Xcode
|
||||||
|
|
||||||
|
## Build generated
|
||||||
|
build/
|
||||||
|
DerivedData/
|
||||||
|
|
||||||
|
## Various settings
|
||||||
|
ExportOptions.plist
|
||||||
|
*.pbxuser
|
||||||
|
!default.pbxuser
|
||||||
|
*.mode1v3
|
||||||
|
!default.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
!default.mode2v3
|
||||||
|
*.perspectivev3
|
||||||
|
!default.perspectivev3
|
||||||
|
xcuserdata/
|
||||||
|
|
||||||
|
## Other
|
||||||
|
*.moved-aside
|
||||||
|
*.xccheckout
|
||||||
|
*.xcscmblueprint
|
||||||
|
|
||||||
|
## We don't want any memgrap's to leak ;)
|
||||||
|
**.memgraph
|
||||||
|
|
||||||
|
## Obj-C/Swift specific
|
||||||
|
*.hmap
|
||||||
|
*.ipa
|
||||||
|
*.dSYM.zip
|
||||||
|
*.dSYM
|
||||||
|
|
||||||
|
## Playgrounds
|
||||||
|
timeline.xctimeline
|
||||||
|
playground.xcworkspace
|
||||||
|
|
||||||
|
# Swift Package Manager
|
||||||
|
#
|
||||||
|
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||||
|
# Packages/
|
||||||
|
# Package.pins
|
||||||
|
# Package.resolved
|
||||||
|
.build/
|
||||||
|
|
||||||
|
# CocoaPods
|
||||||
|
#
|
||||||
|
# We recommend against adding the Pods directory to your .gitignore. However
|
||||||
|
# you should judge for yourself, the pros and cons are mentioned at:
|
||||||
|
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||||
|
#
|
||||||
|
# Pods/
|
||||||
|
#
|
||||||
|
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||||
|
# *.xcworkspace
|
||||||
|
|
||||||
|
# Carthage
|
||||||
|
#
|
||||||
|
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||||
|
# Carthage/Checkouts
|
||||||
|
|
||||||
|
Carthage/Build
|
||||||
|
|
||||||
|
# Accio dependency management
|
||||||
|
Dependencies/
|
||||||
|
.accio/
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
#
|
||||||
|
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||||
|
# screenshots whenever they are needed.
|
||||||
|
# For more information about the recommended setup visit:
|
||||||
|
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||||
|
|
||||||
|
fastlane/report.xml
|
||||||
|
fastlane/Preview.html
|
||||||
|
fastlane/screenshots/**/*.png
|
||||||
|
fastlane/test_output
|
||||||
|
fastlane/*.env
|
||||||
|
|
||||||
|
# Code Injection
|
||||||
|
#
|
||||||
|
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
||||||
|
# https://github.com/johnno1962/injectionforxcode
|
||||||
|
|
||||||
|
iOSInjectionProject/
|
||||||
|
|
||||||
|
# Extension stuff
|
||||||
|
Shared (Extension)/Resources/_locales/
|
||||||
|
Shared (Extension)/Resources/images/
|
||||||
|
Shared (Extension)/Resources/background-bundle.js
|
||||||
|
Shared (Extension)/Resources/content-bundle.js
|
||||||
|
Shared (Extension)/Resources/manifest.json
|
||||||
|
Shared (Extension)/Resources/preauth.html
|
||||||
|
Shared (Extension)/Resources/preauth.js
|
||||||
19
services/apple/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
ARG APP_USER_UID=1000
|
||||||
|
ARG APP_USER_GID=1000
|
||||||
|
ARG IMAGE_ID=development.00000000
|
||||||
|
|
||||||
|
FROM docker-hosted.nexus.bthlabs.pl/hotpocket/base:build-node-20250819-01 AS development
|
||||||
|
|
||||||
|
ARG APP_USER_UID
|
||||||
|
ARG APP_USER_GID
|
||||||
|
ARG IMAGE_ID
|
||||||
|
|
||||||
|
# COPY --chown=$APP_USER_UID:$APP_USER_GID apple/ops/bin/*.sh /srv/bin/
|
||||||
|
|
||||||
|
VOLUME ["/srv/node_modules", "/srv/venv"]
|
||||||
|
|
||||||
|
FROM development AS ci
|
||||||
|
|
||||||
|
COPY --chown=$APP_USER_UID:$APP_USER_GID apple/ /srv/app/
|
||||||
|
COPY --chown=$APP_USER_UID:$APP_USER_GID packages/ /srv/packages/
|
||||||
|
COPY --chown=$APP_USER_UID:$APP_USER_GID tls/ /srv/tls/
|
||||||
1332
services/apple/HotPocket.xcodeproj/project.pbxproj
Normal file
7
services/apple/HotPocket.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2600"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (iOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</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">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (iOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (iOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2600"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (macOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</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">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (macOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (macOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2600"
|
||||||
|
wasCreatedForAppExtension = "YES"
|
||||||
|
version = "2.0">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAD42E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket Extension.appex"
|
||||||
|
BlueprintName = "HotPocket Extension (iOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (iOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = ""
|
||||||
|
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||||
|
launchStyle = "0"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (iOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (iOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2600"
|
||||||
|
wasCreatedForAppExtension = "YES"
|
||||||
|
version = "2.0">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCADE2E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket Extension.appex"
|
||||||
|
BlueprintName = "HotPocket Extension (macOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (macOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = ""
|
||||||
|
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||||
|
launchStyle = "0"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (macOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (macOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2600"
|
||||||
|
wasCreatedForAppExtension = "YES"
|
||||||
|
version = "2.0">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CBCEA4E2E81CB9500722009"
|
||||||
|
BuildableName = "Save to HotPocket.appex"
|
||||||
|
BlueprintName = "macOS (Share Extension)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (macOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = ""
|
||||||
|
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||||
|
launchStyle = "0"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (macOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
|
||||||
|
BuildableName = "HotPocket.app"
|
||||||
|
BlueprintName = "HotPocket (macOS)"
|
||||||
|
ReferencedContainer = "container:HotPocket.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
3
services/apple/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# HotPocket by BTHLabs
|
||||||
|
|
||||||
|
This repository contains the _HotPocket Apple Integrations_ project.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0xED",
|
||||||
|
"green" : "0xBA",
|
||||||
|
"red" : "0x1C"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "icon-1024.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filename" : "icon-1024 1.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "tinted"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filename" : "icon-1024 2.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "icon-16.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "icon-32.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "icon-32 1.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "icon-64.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "512x512"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "icon-mac-1024.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 874 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 117 KiB |
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x29",
|
||||||
|
"green" : "0x25",
|
||||||
|
"red" : "0x21"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x76",
|
||||||
|
"green" : "0x64",
|
||||||
|
"red" : "0xEE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
23
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "icon-large-128.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "icon-large-128@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "icon-large-128@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon-large-128.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon-large-128@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
services/apple/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon-large-128@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 63 KiB |
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0xDD",
|
||||||
|
"green" : "0x82",
|
||||||
|
"red" : "0x00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x67",
|
||||||
|
"green" : "0xA7",
|
||||||
|
"red" : "0x0E"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x2E",
|
||||||
|
"green" : "0x96",
|
||||||
|
"red" : "0xF1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
32
services/apple/Shared (App)/HPAPI.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// HPAPI.h
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 23/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import "HPRPCClient.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
typedef void (^HPAPICheckAuthCompletionHandler)(BOOL authValid, NSError * _Nullable error, NSString * _Nullable callId);
|
||||||
|
|
||||||
|
@interface HPAPI : NSObject
|
||||||
|
|
||||||
|
@property (nullable) HPRPCClient *rpcClient;
|
||||||
|
@property NSMutableSet *callIds;
|
||||||
|
|
||||||
|
-(id)initWithRPCClientDelegate:(id<HPRPCClientDelegate>)delegate;
|
||||||
|
|
||||||
|
+(NSDictionary *)getAccessTokenMeta;
|
||||||
|
|
||||||
|
-(NSString *)checkAuth;
|
||||||
|
-(void)checkAuth:(HPAPICheckAuthCompletionHandler)completionHandler;
|
||||||
|
-(NSString *)save:(NSURL *)url;
|
||||||
|
-(BOOL)save:(NSURL *)url completionHandler:(HPRPCClientCompletionHandler)completionHandler;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
145
services/apple/Shared (App)/HPAPI.m
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
//
|
||||||
|
// HPAPI.m
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 23/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "HPAPI.h"
|
||||||
|
|
||||||
|
#import "HPCredentialsHelper.h"
|
||||||
|
#import "HPRPCClient.h"
|
||||||
|
|
||||||
|
@implementation HPAPI (HPAPIPrivate)
|
||||||
|
|
||||||
|
#pragma mark - Private interface
|
||||||
|
|
||||||
|
-(void)updateRPCClientCredentials {
|
||||||
|
HPCredentials *credentials = [[HPCredentialsHelper sharedHelper] getCredentials];
|
||||||
|
self.rpcClient.baseURL = credentials.rpcURL;
|
||||||
|
self.rpcClient.accessToken = credentials.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation HPAPI
|
||||||
|
|
||||||
|
#pragma mark - Initialization
|
||||||
|
|
||||||
|
-(id)init {
|
||||||
|
if (self = [super init]) {
|
||||||
|
self.rpcClient = [[HPRPCClient alloc] initWithBaseURL:nil accessToken:nil];
|
||||||
|
|
||||||
|
[self updateRPCClientCredentials];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(onCredentialsChanged:)
|
||||||
|
name:@"HPCredentialsChanged"
|
||||||
|
object:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(id)initWithRPCClientDelegate:(id<HPRPCClientDelegate>)rpcClientDelegate {
|
||||||
|
if (self = [self init]) {
|
||||||
|
self.rpcClient.delegate = rpcClientDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)dealloc {
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Public interface
|
||||||
|
|
||||||
|
+(NSDictionary *)getAccessTokenMeta {
|
||||||
|
NSBundle *mainBundle = [NSBundle mainBundle];
|
||||||
|
|
||||||
|
return @{
|
||||||
|
@"version": [mainBundle.infoDictionary valueForKey:@"CFBundleShortVersionString"],
|
||||||
|
@"platform": [mainBundle.infoDictionary valueForKey:@"HPAPIAccessTokenPlatform"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString *)checkAuth {
|
||||||
|
if (self.rpcClient.hasCredentials == NO) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *callId = [[NSUUID UUID] UUIDString];
|
||||||
|
|
||||||
|
BOOL callResult = [self.rpcClient call:callId
|
||||||
|
method:@"accounts.auth.check_access_token"
|
||||||
|
params:@[self.rpcClient.accessToken, [HPAPI getAccessTokenMeta]]
|
||||||
|
endopoint:@"/accounts/rpc/"];
|
||||||
|
if (callResult == NO) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return callId;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)checkAuth:(HPAPICheckAuthCompletionHandler)completionHandler {
|
||||||
|
if (self.rpcClient.hasCredentials == NO) {
|
||||||
|
completionHandler(NO, nil, nil);
|
||||||
|
} else {
|
||||||
|
NSString *callId = [[NSUUID UUID] UUIDString];
|
||||||
|
BOOL callResult = [self.rpcClient call:callId
|
||||||
|
method:@"accounts.auth.check_access_token"
|
||||||
|
params:@[self.rpcClient.accessToken, [HPAPI getAccessTokenMeta]]
|
||||||
|
endopoint:@"/accounts/rpc/" completionHandler:^(NSString *finalCallId, HPRPCCallResult *result) {
|
||||||
|
BOOL authValid = YES;
|
||||||
|
if (result.error != nil) {
|
||||||
|
authValid = NO;
|
||||||
|
} else if ([(NSNumber *)result.result boolValue] == NO) {
|
||||||
|
authValid = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHandler(authValid, result.error, finalCallId);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString *)save:(NSURL *)url {
|
||||||
|
if (self.rpcClient.hasCredentials == NO) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url == nil) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *callId = [[NSUUID UUID] UUIDString];
|
||||||
|
BOOL callResult = [self.rpcClient call:callId method:@"saves.create" params:@[[url absoluteString]]];
|
||||||
|
if (callResult == NO) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return callId;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)save:(NSURL *)url completionHandler:(HPRPCClientCompletionHandler)completionHandler {
|
||||||
|
if (self.rpcClient.hasCredentials == NO) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url == nil) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *callId = [[NSUUID UUID] UUIDString];
|
||||||
|
return [self.rpcClient call:callId
|
||||||
|
method:@"saves.create"
|
||||||
|
params:@[[url absoluteString]]
|
||||||
|
completionHandler:completionHandler];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Notification handlers
|
||||||
|
|
||||||
|
-(void)onCredentialsChanged:(NSNotification *)notification {
|
||||||
|
[self updateRPCClientCredentials];
|
||||||
|
}
|
||||||
|
@end
|
||||||
30
services/apple/Shared (App)/HPAuthFlow.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// HPAuthFlow.h
|
||||||
|
// HotPocket (macOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 21/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface HPAuthParams : NSObject
|
||||||
|
|
||||||
|
@property (copy) NSString *authKey;
|
||||||
|
@property (copy) NSString *sessionToken;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface HPAuthFlow : NSObject
|
||||||
|
|
||||||
|
@property (nullable) NSURL *baseURL;
|
||||||
|
@property (nullable) NSString *sessionToken;
|
||||||
|
|
||||||
|
-(NSURL *)start;
|
||||||
|
-(HPAuthParams *)handlePostAuthenticateURL:(NSURL *)url;
|
||||||
|
-(BOOL)handleAuthParams:(HPAuthParams *)authParams;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
140
services/apple/Shared (App)/HPAuthFlow.m
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
//
|
||||||
|
// HPAuthFlow.m
|
||||||
|
// HotPocket (macOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 21/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "HPAuthFlow.h"
|
||||||
|
|
||||||
|
#import "HPAPI.h"
|
||||||
|
#import "HPCredentialsHelper.h"
|
||||||
|
#import "HPRPCClient.h"
|
||||||
|
#import "NSURL+HotPocketExtensions.h"
|
||||||
|
|
||||||
|
@implementation HPAuthParams
|
||||||
|
|
||||||
|
#pragma mark - HPAuthParams implementation
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation HPAuthFlow (HPAuthFlowPrivate)
|
||||||
|
|
||||||
|
#pragma mark - HPAuthFlow private interface
|
||||||
|
|
||||||
|
-(NSURL *)resolveAuthenticateURL {
|
||||||
|
if (self.baseURL == nil) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.baseURL.isUsableInHotPocket == NO) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSBundle *mainBundle = [NSBundle mainBundle];
|
||||||
|
|
||||||
|
NSURLComponents *authURLComponents = [NSURLComponents componentsWithURL:self.baseURL resolvingAgainstBaseURL:NO];
|
||||||
|
authURLComponents.path = @"/integrations/extension/authenticate/";
|
||||||
|
authURLComponents.queryItems = @[
|
||||||
|
[NSURLQueryItem queryItemWithName:@"source" value:[[mainBundle infoDictionary] valueForKey:@"HPAuthFlowSource"]],
|
||||||
|
[NSURLQueryItem queryItemWithName:@"session_token" value:self.sessionToken],
|
||||||
|
];
|
||||||
|
|
||||||
|
return authURLComponents.URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation HPAuthFlow
|
||||||
|
|
||||||
|
#pragma mark - Initialization
|
||||||
|
|
||||||
|
-(id)init {
|
||||||
|
if (self = [super init]) {
|
||||||
|
self.baseURL = nil;
|
||||||
|
self.sessionToken = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Public interface
|
||||||
|
|
||||||
|
-(NSURL *)start {
|
||||||
|
if (self.baseURL == nil) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.sessionToken == nil) {
|
||||||
|
self.sessionToken = [[NSUUID UUID] UUIDString];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [self resolveAuthenticateURL];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(HPAuthParams *)handlePostAuthenticateURL:(NSURL *)url {
|
||||||
|
if (url == nil) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSDictionary *postAuthenticateURLParams = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"HPAuthFlowPostAuthenticateURLParts"];
|
||||||
|
if (postAuthenticateURLParams == nil) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
||||||
|
|
||||||
|
if ([urlComponents.scheme isEqualToString:[postAuthenticateURLParams valueForKey:@"scheme"]] == NO) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([urlComponents.host isEqualToString:[postAuthenticateURLParams valueForKey:@"host"]] == NO) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
HPAuthParams *result = [[HPAuthParams alloc] init];
|
||||||
|
for (NSURLQueryItem *queryItem in urlComponents.queryItems) {
|
||||||
|
if ([queryItem.name isEqualToString:@"auth_key"] == YES) {
|
||||||
|
result.authKey = queryItem.value;
|
||||||
|
} else if ([queryItem.name isEqualToString:@"session_token"] == YES) {
|
||||||
|
result.sessionToken = queryItem.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.sessionToken isEqualToString:result.sessionToken] == NO) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)handleAuthParams:(HPAuthParams *)authParams {
|
||||||
|
HPRPCClient *rpcClient = [[HPRPCClient alloc] initWithBaseURL:self.baseURL accessToken:nil];
|
||||||
|
|
||||||
|
NSArray *callParams = @[
|
||||||
|
authParams.authKey,
|
||||||
|
[HPAPI getAccessTokenMeta],
|
||||||
|
];
|
||||||
|
|
||||||
|
BOOL callResult = [rpcClient call:self.sessionToken
|
||||||
|
method:@"accounts.access_tokens.create"
|
||||||
|
params:callParams endopoint:@"/accounts/rpc/"
|
||||||
|
completionHandler:^(NSString *callId, HPRPCCallResult *result) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
if (result.error != nil) {
|
||||||
|
NSLog(@"-[HPAuthFlow handleAuthParams:] error=`%@`", result.error);
|
||||||
|
} else {
|
||||||
|
HPCredentialsHelper *credentialsHelper = [HPCredentialsHelper sharedHelper];
|
||||||
|
[credentialsHelper saveCredentials:[self.baseURL absoluteString] accessToken:(NSString *)result.result];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sessionToken = nil;
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:@"AuthFlowDidFinish" object:self];
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
|
||||||
|
return callResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
32
services/apple/Shared (App)/HPCredentialsHelper.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// HPCredentialsHelper.h
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 19/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface HPCredentials : NSObject
|
||||||
|
|
||||||
|
@property (nullable) NSString *baseURL;
|
||||||
|
@property (nullable) NSString *accessToken;
|
||||||
|
|
||||||
|
@property (readonly) BOOL usable;
|
||||||
|
@property (readonly) NSURL *rpcURL;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface HPCredentialsHelper : NSObject
|
||||||
|
|
||||||
|
+(instancetype)sharedHelper;
|
||||||
|
|
||||||
|
-(HPCredentials *)getCredentials;
|
||||||
|
-(BOOL)saveCredentials:(NSString *)baseURL accessToken:(NSString *)accessToken;
|
||||||
|
-(BOOL)clearCredentials;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
218
services/apple/Shared (App)/HPCredentialsHelper.m
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
//
|
||||||
|
// HPCredentialsHelper.m
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 19/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Security/Security.h>
|
||||||
|
|
||||||
|
#import "HPCredentialsHelper.h"
|
||||||
|
|
||||||
|
@implementation HPCredentials
|
||||||
|
|
||||||
|
#pragma mark - HPCredentials implementation
|
||||||
|
|
||||||
|
-(id)init {
|
||||||
|
if (self = [super init]) {
|
||||||
|
self.baseURL = nil;
|
||||||
|
self.accessToken = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)usable {
|
||||||
|
return (self.baseURL != nil && self.accessToken != nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSURL *)rpcURL {
|
||||||
|
return [NSURL URLWithString:self.baseURL];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString *)description {
|
||||||
|
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:2];
|
||||||
|
|
||||||
|
if (self.baseURL == nil) {
|
||||||
|
[attributes setValue:@"(null)" forKey:@"baseURL"];
|
||||||
|
} else {
|
||||||
|
[attributes setValue:self.baseURL forKey:@"baseURL"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.accessToken == nil) {
|
||||||
|
[attributes setValue:@"(null)" forKey:@"accessToken"];
|
||||||
|
} else {
|
||||||
|
[attributes setValue:@"***" forKey:@"accessToken"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [NSString stringWithFormat:@"<%@: %p; %@>", NSStringFromClass([self class]), self, attributes];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation HPCredentialsHelper (HPCredentialsHelperPrivate)
|
||||||
|
|
||||||
|
#pragma mark - Private interface
|
||||||
|
|
||||||
|
-(NSString *)getService {
|
||||||
|
#ifdef DEBUG
|
||||||
|
return @"pl.bthlabs.HotPocket.Debug";
|
||||||
|
#else
|
||||||
|
return @"pl.bthlabs.HotPocket";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSData *)getKeychainItem:(NSString *)service account:(NSString *)account {
|
||||||
|
if (service == nil || account == nil) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSDictionary *query = @{
|
||||||
|
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
||||||
|
(__bridge id)kSecAttrService: service,
|
||||||
|
(__bridge id)kSecAttrAccount: account,
|
||||||
|
(__bridge id)kSecReturnData: @YES,
|
||||||
|
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
|
||||||
|
(__bridge id)kSecAttrAccessGroup: @"648728X64K.pl.bthlabs.HotPocketShared",
|
||||||
|
(__bridge id)kSecUseDataProtectionKeychain: @YES,
|
||||||
|
};
|
||||||
|
|
||||||
|
CFTypeRef resultData = NULL;
|
||||||
|
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &resultData);
|
||||||
|
|
||||||
|
if (status == errSecSuccess && resultData != NULL) {
|
||||||
|
NSData *result = (__bridge_transfer NSData *)resultData;
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
CFStringRef statusStringRef = SecCopyErrorMessageString(status, NULL);
|
||||||
|
NSString *statusString = (__bridge NSString *)statusStringRef;
|
||||||
|
NSLog(@"-[HPCredentialsHelper getKeychainItem:account:] service=`%@` account=`%@` status=%@", service, account, statusString);
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)createKeychainItemWithValue:(NSData *)value service:(NSString *)service account:(NSString *)account {
|
||||||
|
if (value == nil || service == nil || account == nil) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSDictionary *attributes = @{
|
||||||
|
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
||||||
|
(__bridge id)kSecAttrService: service,
|
||||||
|
(__bridge id)kSecAttrAccount: account,
|
||||||
|
(__bridge id)kSecValueData: value,
|
||||||
|
(__bridge id)kSecAttrAccessGroup: @"648728X64K.pl.bthlabs.HotPocketShared",
|
||||||
|
(__bridge id)kSecUseDataProtectionKeychain: @YES,
|
||||||
|
};
|
||||||
|
|
||||||
|
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
|
||||||
|
|
||||||
|
if (status != errSecSuccess) {
|
||||||
|
CFStringRef statusStringRef = SecCopyErrorMessageString(status, NULL);
|
||||||
|
NSString *statusString = (__bridge NSString *)statusStringRef;
|
||||||
|
NSLog(@"-[HPCredentialsHelper createKeychainItemWithValue:service:account:] service=`%@` account=`%@` status=%@", service, account, statusString);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)deleteKeychainItem:(NSString *)service account:(NSString *)account {
|
||||||
|
if (service == nil || account == nil) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSDictionary *query = @{
|
||||||
|
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
||||||
|
(__bridge id)kSecAttrService: service,
|
||||||
|
(__bridge id)kSecAttrAccount: account,
|
||||||
|
(__bridge id)kSecAttrAccessGroup: @"648728X64K.pl.bthlabs.HotPocketShared",
|
||||||
|
(__bridge id)kSecUseDataProtectionKeychain: @YES,
|
||||||
|
};
|
||||||
|
|
||||||
|
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
|
||||||
|
|
||||||
|
if (status != errSecSuccess) {
|
||||||
|
CFStringRef statusStringRef = SecCopyErrorMessageString(status, NULL);
|
||||||
|
NSString *statusString = (__bridge NSString *)statusStringRef;
|
||||||
|
NSLog(@"-[HPCredentialsHelper deleteKeychainItem:account:] service=`%@` account=`%@` status=%@", service, account, statusString);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation HPCredentialsHelper
|
||||||
|
|
||||||
|
#pragma mark - Initialization
|
||||||
|
|
||||||
|
+(instancetype)sharedHelper {
|
||||||
|
static HPCredentialsHelper *sharedInstance = nil;
|
||||||
|
static dispatch_once_t initToken;
|
||||||
|
dispatch_once(&initToken, ^{
|
||||||
|
sharedInstance = [[self alloc] init];
|
||||||
|
});
|
||||||
|
|
||||||
|
return sharedInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Public interface
|
||||||
|
|
||||||
|
-(HPCredentials *)getCredentials {
|
||||||
|
HPCredentials *result = [[HPCredentials alloc] init];
|
||||||
|
|
||||||
|
NSData *itemData = [self getKeychainItem:[self getService] account:@"RPC"];
|
||||||
|
if (itemData != nil) {
|
||||||
|
NSError *error;
|
||||||
|
NSDictionary *itemPayload = [NSJSONSerialization JSONObjectWithData:itemData
|
||||||
|
options:NSJSONReadingTopLevelDictionaryAssumed
|
||||||
|
error:&error];
|
||||||
|
|
||||||
|
if (error != nil) {
|
||||||
|
NSLog(@"-[HPCredentialsHalper getCredentials] error=`%@`", error);
|
||||||
|
} else if (itemPayload != nil) {
|
||||||
|
result.baseURL = [itemPayload valueForKey:@"baseURL"];
|
||||||
|
result.accessToken = [itemPayload valueForKey:@"accessToken"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)saveCredentials:(NSString *)baseURL accessToken:(NSString *)accessToken {
|
||||||
|
NSMutableDictionary *itemPayload = [NSMutableDictionary dictionaryWithCapacity:2];
|
||||||
|
|
||||||
|
if (baseURL != nil) {
|
||||||
|
[itemPayload setValue:baseURL forKey:@"baseURL"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessToken != nil) {
|
||||||
|
[itemPayload setValue:accessToken forKey:@"accessToken"];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError *error;
|
||||||
|
NSData *itemData = [NSJSONSerialization dataWithJSONObject:itemPayload options:0 error:&error];
|
||||||
|
|
||||||
|
if (error != nil) {
|
||||||
|
NSLog(@"-[HPCredentialsHalper saveCredentials:accessToken:] error=`%@`", error);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL saveResult = [self createKeychainItemWithValue:itemData service:[self getService] account:@"RPC"];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:@"HPCredentialsChanged" object:self];
|
||||||
|
|
||||||
|
return saveResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)clearCredentials {
|
||||||
|
BOOL deleteResult = [self deleteKeychainItem:[self getService] account:@"RPC"];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:@"HPCredentialsChanged" object:self];
|
||||||
|
|
||||||
|
return deleteResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
44
services/apple/Shared (App)/HPRPCClient.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// HPRPCClient.h
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 19/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface HPRPCCallResult : NSObject
|
||||||
|
|
||||||
|
@property (nullable) NSError *error;
|
||||||
|
@property (nullable) id result;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@protocol HPRPCClientDelegate <NSObject>
|
||||||
|
|
||||||
|
-(void)rpcClientDidReceiveResult:(HPRPCCallResult *)result callId:(NSString *)callId;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
typedef void (^HPRPCClientCompletionHandler)(NSString * _Nullable callId, HPRPCCallResult * _Nullable result);
|
||||||
|
|
||||||
|
@interface HPRPCClient : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, weak) id<HPRPCClientDelegate> delegate;
|
||||||
|
@property NSURL *baseURL;
|
||||||
|
@property NSString *accessToken;
|
||||||
|
@property NSURLSession *session;
|
||||||
|
|
||||||
|
-(id)initWithBaseURL:(nullable NSURL *)baseURL accessToken:(nullable NSString *)accessToken;
|
||||||
|
|
||||||
|
-(BOOL)hasCredentials;
|
||||||
|
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params endopoint:(nullable NSString *)endpoint completionHandler:(HPRPCClientCompletionHandler)completionHandler;
|
||||||
|
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params endopoint:(nullable NSString *)endpoint;
|
||||||
|
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params;
|
||||||
|
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params completionHandler:(HPRPCClientCompletionHandler)completionHandler;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
186
services/apple/Shared (App)/HPRPCClient.m
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
//
|
||||||
|
// HPRPCClient.m
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 19/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "HPRPCClient.h"
|
||||||
|
|
||||||
|
@implementation HPRPCCallResult
|
||||||
|
|
||||||
|
#pragma mark - HPRPCCallResult implementation
|
||||||
|
|
||||||
|
-(id)init {
|
||||||
|
if (self = [super init]) {
|
||||||
|
self.error = nil;
|
||||||
|
self.result = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString *)description {
|
||||||
|
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:2];
|
||||||
|
|
||||||
|
if (self.error == nil) {
|
||||||
|
[attributes setValue:@"(null)" forKey:@"error"];
|
||||||
|
} else {
|
||||||
|
[attributes setValue:self.error forKey:@"error"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.result == nil) {
|
||||||
|
[attributes setValue:@"(null)" forKey:@"result"];
|
||||||
|
} else {
|
||||||
|
[attributes setValue:self.result forKey:@"result"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [NSString stringWithFormat:@"<%@: %p; %@>", NSStringFromClass([self class]), self, attributes];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation HPRPCClient
|
||||||
|
|
||||||
|
#pragma mark - Initialization
|
||||||
|
|
||||||
|
-(id)initWithBaseURL:(nullable NSURL *)baseURL accessToken:(nullable NSString *)accessToken {
|
||||||
|
if (self = [super init]) {
|
||||||
|
self.baseURL = baseURL;
|
||||||
|
self.accessToken = accessToken;
|
||||||
|
self.session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.ephemeralSessionConfiguration];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Public interface
|
||||||
|
|
||||||
|
-(BOOL)hasCredentials {
|
||||||
|
return (self.baseURL != nil && self.accessToken != nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params endopoint:(nullable NSString *)endpoint completionHandler:(HPRPCClientCompletionHandler)completionHandler {
|
||||||
|
if (self.baseURL == nil) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callId == nil) {
|
||||||
|
callId = [[NSUUID UUID] UUIDString];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpoint == nil) {
|
||||||
|
endpoint = @"/rpc/";
|
||||||
|
}
|
||||||
|
|
||||||
|
NSBundle *mainBundle = [NSBundle mainBundle];
|
||||||
|
|
||||||
|
NSDictionary *payload = @{
|
||||||
|
@"jsonrpc": @"2.0",
|
||||||
|
@"id": callId,
|
||||||
|
@"method": method,
|
||||||
|
@"params": params,
|
||||||
|
};
|
||||||
|
|
||||||
|
NSError *error;
|
||||||
|
NSData *jsonPayload = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&error];
|
||||||
|
if (!jsonPayload) {
|
||||||
|
NSLog(@"-[HPRPCClient call:method:params:endpoint:] Unable to serialize payload: error=`%@`", error);
|
||||||
|
HPRPCCallResult *result = [[HPRPCCallResult alloc] init];
|
||||||
|
result.error = error;
|
||||||
|
|
||||||
|
if (self.delegate != nil) {
|
||||||
|
[self.delegate rpcClientDidReceiveResult:result callId:callId];
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:self.baseURL resolvingAgainstBaseURL:NO];
|
||||||
|
urlComponents.path = endpoint;
|
||||||
|
urlComponents.queryItems = @[
|
||||||
|
[NSURLQueryItem queryItemWithName:@"method" value:method],
|
||||||
|
];
|
||||||
|
|
||||||
|
NSURL *callURL = [urlComponents URL];
|
||||||
|
#ifdef DEBUG
|
||||||
|
NSLog(@"-[HPRPCClient call:method:params:endpoint:] callURL=`%@`", callURL.absoluteString);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:callURL];
|
||||||
|
request.HTTPMethod = @"POST";
|
||||||
|
request.HTTPBody = jsonPayload;
|
||||||
|
|
||||||
|
[request setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
|
||||||
|
[request setValue:[[mainBundle infoDictionary] valueForKey:@"HPRPCClientOrigin"] forHTTPHeaderField:@"Origin"];
|
||||||
|
|
||||||
|
if (self.accessToken != nil) {
|
||||||
|
NSString *authorization = [NSString stringWithFormat:@"Bearer %@", self.accessToken];
|
||||||
|
[request setValue:authorization forHTTPHeaderField:@"Authorization"];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *build = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||||
|
NSString *userAgent = [NSString stringWithFormat:@"HotPocket/%@", build];
|
||||||
|
[request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
|
||||||
|
|
||||||
|
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request
|
||||||
|
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||||
|
HPRPCCallResult *result = [[HPRPCCallResult alloc] init];
|
||||||
|
if (error != nil) {
|
||||||
|
result.error = error;
|
||||||
|
} else {
|
||||||
|
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||||
|
if (httpResponse.statusCode != 200) {
|
||||||
|
result.error = [[NSError alloc] initWithDomain:@"pl.bthlabs.HotPocket.HPRPCClient" code:-32000 userInfo:@{
|
||||||
|
@"callId": callId,
|
||||||
|
@"url": httpResponse.URL,
|
||||||
|
@"statusCode": [NSNumber numberWithInteger:httpResponse.statusCode],
|
||||||
|
@"response": response,
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
NSError *jsonDecodeError;
|
||||||
|
NSDictionary *callResult = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingTopLevelDictionaryAssumed error:&error];
|
||||||
|
if (jsonDecodeError != nil) {
|
||||||
|
result.error = jsonDecodeError;
|
||||||
|
} else {
|
||||||
|
NSDictionary *rpcError = [callResult valueForKey:@"error"];
|
||||||
|
if (rpcError != nil) {
|
||||||
|
NSNumber *rpcErrorCode = [rpcError valueForKey:@"code"];
|
||||||
|
if (rpcErrorCode == nil) {
|
||||||
|
rpcErrorCode = [NSNumber numberWithInt:-32000];
|
||||||
|
}
|
||||||
|
|
||||||
|
result.error = [[NSError alloc] initWithDomain:@"pl.bthlabs.HotPocket.HPRPCClient" code:[rpcErrorCode integerValue] userInfo:rpcError];
|
||||||
|
} else {
|
||||||
|
result.result = [callResult valueForKey:@"result"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completionHandler) {
|
||||||
|
completionHandler(callId, result);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
[task resume];
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params endopoint:(nullable NSString *)endpoint {
|
||||||
|
return [self call:callId method:method params:params endopoint:endpoint completionHandler:^(NSString *callId, HPRPCCallResult *result) {
|
||||||
|
if (self.delegate != nil) {
|
||||||
|
[self.delegate rpcClientDidReceiveResult:result callId:callId];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params {
|
||||||
|
return [self call:callId method:method params:params endopoint:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params completionHandler:(HPRPCClientCompletionHandler)completionHandler {
|
||||||
|
return [self call:callId method:method params:params endopoint:nil completionHandler:completionHandler];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
18
services/apple/Shared (App)/NSURL+HotPocketExtensions.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// NSURL+HotPocketExtensions.h
|
||||||
|
// HotPocket (macOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 30/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface NSURL (HotPocketExtensions)
|
||||||
|
|
||||||
|
-(BOOL)isUsableInHotPocket;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
30
services/apple/Shared (App)/NSURL+HotPocketExtensions.m
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// NSURL+HotPocketExtensions.m
|
||||||
|
// HotPocket (macOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 30/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "NSURL+HotPocketExtensions.h"
|
||||||
|
|
||||||
|
@implementation NSURL (HotPocketExtensions)
|
||||||
|
|
||||||
|
-(BOOL)isUsableInHotPocket {
|
||||||
|
static NSArray *supportedSchemes = @[@"http", @"https"];
|
||||||
|
|
||||||
|
if (self.baseURL != nil) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.scheme == nil || [supportedSchemes containsObject:self.scheme] == NO) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.host == nil || [@"" isEqualToString:self.host] == YES) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
BIN
services/apple/Shared (App)/Resources/icon-mac-384.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// SafariWebExtensionHandler.h
|
||||||
|
// Shared (Extension)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 21/08/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@interface SafariWebExtensionHandler : NSObject <NSExtensionRequestHandling>
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// SafariWebExtensionHandler.m
|
||||||
|
// Shared (Extension)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 21/08/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SafariWebExtensionHandler.h"
|
||||||
|
|
||||||
|
#import <SafariServices/SafariServices.h>
|
||||||
|
|
||||||
|
@implementation SafariWebExtensionHandler
|
||||||
|
|
||||||
|
- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context {
|
||||||
|
NSExtensionItem *request = context.inputItems.firstObject;
|
||||||
|
|
||||||
|
NSUUID *profile;
|
||||||
|
if (@available(iOS 17.0, macOS 14.0, *)) {
|
||||||
|
profile = request.userInfo[SFExtensionProfileKey];
|
||||||
|
} else {
|
||||||
|
profile = request.userInfo[@"profile"];
|
||||||
|
}
|
||||||
|
|
||||||
|
id message;
|
||||||
|
if (@available(iOS 15.0, macOS 11.0, *)) {
|
||||||
|
message = request.userInfo[SFExtensionMessageKey];
|
||||||
|
} else {
|
||||||
|
message = request.userInfo[@"message"];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSLog(@"Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", message, profile.UUIDString ?: @"none");
|
||||||
|
|
||||||
|
NSExtensionItem *response = [[NSExtensionItem alloc] init];
|
||||||
|
if (@available(iOS 15.0, macOS 11.0, *)) {
|
||||||
|
response.userInfo = @{ SFExtensionMessageKey: @{ @"echo": message } };
|
||||||
|
} else {
|
||||||
|
response.userInfo = @{ @"message": @{ @"echo": message } };
|
||||||
|
}
|
||||||
|
|
||||||
|
[context completeRequestReturningItems:@[ response ] completionHandler:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// HPShareExtensionHelper.h
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 27/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@class HPSharedItemsContainer;
|
||||||
|
|
||||||
|
typedef void (^HPShareExtensionHelperHandleItemsCompletionHandler)(NSURL * _Nullable url);
|
||||||
|
|
||||||
|
@interface HPShareExtensionHelper : NSObject
|
||||||
|
|
||||||
|
@property NSExtensionContext *context;
|
||||||
|
@property HPSharedItemsContainer *items;
|
||||||
|
|
||||||
|
-(instancetype)initWithContext:(NSExtensionContext *)context;
|
||||||
|
|
||||||
|
-(void)processItems:(HPShareExtensionHelperHandleItemsCompletionHandler)completionHandler;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
102
services/apple/Shared (Share Extension)/HPShareExtensionHelper.m
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
//
|
||||||
|
// HPShareExtensionHelper.m
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 27/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||||
|
|
||||||
|
#import "HPShareExtensionHelper.h"
|
||||||
|
|
||||||
|
#import "HPSharedItem.h"
|
||||||
|
#import "HPSharedItemsContainer.h"
|
||||||
|
|
||||||
|
@implementation HPShareExtensionHelper (HPShareExtensionHelperPrivate)
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation HPShareExtensionHelper
|
||||||
|
|
||||||
|
-(instancetype)initWithContext:(NSExtensionContext *)context {
|
||||||
|
if (self = [super init]) {
|
||||||
|
self.context = context;
|
||||||
|
self.items = [[HPSharedItemsContainer alloc] init];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)processItems:(HPShareExtensionHelperHandleItemsCompletionHandler)completionHandler {
|
||||||
|
// Depending on the app, the URL might be stored in `public.url` attachment or elsewhere.
|
||||||
|
// For example, the YouTube app passes it in `public.plain-text`. Because of course it does.
|
||||||
|
// Furthermore, for some bizarre reason the recommended way of extracting the URL when sharing from a browser
|
||||||
|
// is to run a JS snippet and examine its output.
|
||||||
|
// This method will iterate through all the shared items and their attachments and attempt to extract
|
||||||
|
// the URL candidates.
|
||||||
|
//
|
||||||
|
// Also note that handler for `public.url` explicitly requests the payload to be corced to `NSURL *`. Leaving it
|
||||||
|
// at `NSData *` would cause iOS to, wait for it!, fetch the URL and dump the response body in the payload :D.
|
||||||
|
//
|
||||||
|
// This is so _so_ *so* dumb. But hey, at least I learned how to to "chords" in CGD ¯\_(ツ)_/¯
|
||||||
|
UTType *propertyListType = [UTType typeWithFilenameExtension:@"plist"];
|
||||||
|
|
||||||
|
dispatch_group_t dispatchGroup = dispatch_group_create();
|
||||||
|
dispatch_queue_t queue = dispatch_queue_create("HPShareExtensionHelper.processItems.queue", DISPATCH_QUEUE_SERIAL);
|
||||||
|
|
||||||
|
for (NSExtensionItem *inputItem in self.context.inputItems) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
NSLog(@"-[HPShareExtensionHelper processItems:] inputItem.userInfo=`%@`", inputItem);
|
||||||
|
#endif
|
||||||
|
[inputItem.attachments enumerateObjectsUsingBlock:^(NSItemProvider *attachment, NSUInteger index, BOOL *stop) {
|
||||||
|
dispatch_group_enter(dispatchGroup);
|
||||||
|
|
||||||
|
if ([attachment hasItemConformingToTypeIdentifier:propertyListType.identifier] == YES) {
|
||||||
|
[attachment loadItemForTypeIdentifier:propertyListType.identifier
|
||||||
|
options:nil
|
||||||
|
completionHandler:^(NSDictionary *payload, NSError *error) {
|
||||||
|
dispatch_async(queue, ^{
|
||||||
|
self.items.primaryItem = [[HPSharedItem alloc] initWithPayload:payload
|
||||||
|
typeIdentifier:propertyListType.identifier
|
||||||
|
error:error];
|
||||||
|
|
||||||
|
dispatch_group_leave(dispatchGroup);
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
} else if ([attachment hasItemConformingToTypeIdentifier:@"public.url"] == YES) {
|
||||||
|
[attachment loadItemForTypeIdentifier:@"public.url"
|
||||||
|
options:nil
|
||||||
|
completionHandler:^(NSURL *payload, NSError *error) {
|
||||||
|
dispatch_async(queue, ^{
|
||||||
|
[self.items.candidateItems addObject:[[HPSharedItem alloc] initWithPayload:payload
|
||||||
|
typeIdentifier:@"public.url"
|
||||||
|
error:error]];
|
||||||
|
|
||||||
|
dispatch_group_leave(dispatchGroup);
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
} else if ([attachment hasItemConformingToTypeIdentifier:@"public.plain-text"] == YES) {
|
||||||
|
[attachment loadItemForTypeIdentifier:@"public.plain-text"
|
||||||
|
options:nil
|
||||||
|
completionHandler:^(NSString *payload, NSError *error) {
|
||||||
|
dispatch_async(queue, ^{
|
||||||
|
[self.items.candidateItems addObject:[[HPSharedItem alloc] initWithPayload:payload
|
||||||
|
typeIdentifier:@"public.plain-text"
|
||||||
|
error:error]];
|
||||||
|
|
||||||
|
dispatch_group_leave(dispatchGroup);
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
dispatch_group_leave(dispatchGroup);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
|
||||||
|
NSURL *result = [self.items resolveURL];
|
||||||
|
completionHandler(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
24
services/apple/Shared (Share Extension)/HPSharedItem.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// HPSharedItem.h
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 27/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface HPSharedItem : NSObject
|
||||||
|
|
||||||
|
@property (nullable) id payload;
|
||||||
|
@property NSString *typeIdentifier;
|
||||||
|
@property (nullable) NSError *error;
|
||||||
|
|
||||||
|
-(instancetype)initWithPayload:(nullable id)payload typeIdentifier:(NSString *)typeIdentifier error:(nullable NSError *)error;
|
||||||
|
|
||||||
|
-(NSURL *)maybeURL;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
47
services/apple/Shared (Share Extension)/HPSharedItem.m
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// HPSharedItem.m
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 27/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||||
|
|
||||||
|
#import "HPSharedItem.h"
|
||||||
|
|
||||||
|
@implementation HPSharedItem
|
||||||
|
|
||||||
|
-(instancetype)initWithPayload:(id)payload typeIdentifier:(NSString *)typeIdentifier error:(NSError *)error {
|
||||||
|
if (self = [super init]) {
|
||||||
|
self.payload = payload;
|
||||||
|
self.typeIdentifier = typeIdentifier;
|
||||||
|
self.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSURL *)maybeURL {
|
||||||
|
if (self.error != nil) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.typeIdentifier isEqualToString:[UTType typeWithFilenameExtension:@"plist"].identifier] == YES) {
|
||||||
|
NSDictionary *propertyList = self.payload;
|
||||||
|
NSDictionary *jsHelperResult = [propertyList valueForKey:NSExtensionJavaScriptPreprocessingResultsKey];
|
||||||
|
|
||||||
|
if ([jsHelperResult valueForKey:@"iHateComputers"] == nil) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [NSURL URLWithString:[jsHelperResult valueForKey:@"url"]];
|
||||||
|
} else if ([self.typeIdentifier isEqualToString:@"public.url"] == YES) {
|
||||||
|
return self.payload;
|
||||||
|
} if ([self.typeIdentifier isEqualToString:@"public.plain-text"] == YES) {
|
||||||
|
return [NSURL URLWithString:self.payload];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// HPSharedItemsContainer.h
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 27/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@class HPSharedItem;
|
||||||
|
|
||||||
|
@interface HPSharedItemsContainer : NSObject
|
||||||
|
|
||||||
|
@property (nullable) HPSharedItem *primaryItem;
|
||||||
|
@property NSMutableArray<HPSharedItem *> *candidateItems;
|
||||||
|
|
||||||
|
-(NSURL *)resolveURL;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// HPSharedItemsContainer.m
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 27/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "HPSharedItemsContainer.h"
|
||||||
|
|
||||||
|
#import "HPSharedItem.h"
|
||||||
|
#import "NSURL+HotPocketExtensions.h"
|
||||||
|
|
||||||
|
@implementation HPSharedItemsContainer (HPSharedItemsContainerPrivate)
|
||||||
|
|
||||||
|
-(NSURL *)validatedURL:(NSURL *)url {
|
||||||
|
if (url.isUsableInHotPocket == NO) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation HPSharedItemsContainer
|
||||||
|
|
||||||
|
-(instancetype)init {
|
||||||
|
if (self = [super init]) {
|
||||||
|
self.primaryItem = nil;
|
||||||
|
self.candidateItems = [[NSMutableArray alloc] initWithCapacity:1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSURL *)resolveURL {
|
||||||
|
NSURL *result = nil;
|
||||||
|
|
||||||
|
if (self.primaryItem != nil) {
|
||||||
|
result = [self validatedURL:[self.primaryItem maybeURL]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.candidateItems count] > 0) {
|
||||||
|
NSUInteger itemCandidateIndex = 0;
|
||||||
|
while (result == nil) {
|
||||||
|
HPSharedItem *itemCandidate = [self.candidateItems objectAtIndex:itemCandidateIndex];
|
||||||
|
|
||||||
|
result = [self validatedURL:itemCandidate.maybeURL];
|
||||||
|
if (result != nil) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemCandidateIndex += 1;
|
||||||
|
if (itemCandidateIndex >= [self.candidateItems count]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
23
services/apple/docker-compose-ci.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
services:
|
||||||
|
apple-ci:
|
||||||
|
build:
|
||||||
|
context: ".."
|
||||||
|
dockerfile: "apple/Dockerfile"
|
||||||
|
target: "development"
|
||||||
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/apple:ci-local"
|
||||||
|
command: "echo 'NOOP'"
|
||||||
|
environment:
|
||||||
|
PYTHONBREAKPOINT: "ipdb.set_trace"
|
||||||
|
HOTPOCKET_PACKAGES_ENV: "${HOTPOCKET_EXTENSION_ENV:-docker}"
|
||||||
|
# REQUESTS_CA_BUNDLE: "/srv/tls/requests_ca_bundle.pem"
|
||||||
|
RUN_POETRY_INSTALL: "true"
|
||||||
|
RUN_YARN_INSTALL: "false"
|
||||||
|
SETUP_BACKEND: "true"
|
||||||
|
SETUP_FRONTEND: "false"
|
||||||
|
volumes:
|
||||||
|
- "apple_venv:/srv/venv"
|
||||||
|
- "apple_node_modules:/srv/node_modules"
|
||||||
|
- "../tls:/srv/tls"
|
||||||
|
restart: "no"
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
29
services/apple/docker-compose.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
services:
|
||||||
|
apple-management:
|
||||||
|
build:
|
||||||
|
context: ".."
|
||||||
|
dockerfile: "apple/Dockerfile"
|
||||||
|
target: "development"
|
||||||
|
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/apple:local"
|
||||||
|
command: "echo 'NOOP'"
|
||||||
|
environment: &apple-env
|
||||||
|
PYTHONBREAKPOINT: "ipdb.set_trace"
|
||||||
|
HOTPOCKET_EXTENSION_ENV: "${HOTPOCKET_EXTENSION_ENV:-docker}"
|
||||||
|
REQUESTS_CA_BUNDLE: "/srv/tls/requests_ca_bundle.pem"
|
||||||
|
RUN_POETRY_INSTALL: "true"
|
||||||
|
RUN_YARN_INSTALL: "false"
|
||||||
|
SETUP_BACKEND: "true"
|
||||||
|
SETUP_FRONTEND: "false"
|
||||||
|
volumes:
|
||||||
|
- "apple_venv:/srv/venv"
|
||||||
|
- "apple_node_modules:/srv/node_modules"
|
||||||
|
- ".:/srv/app"
|
||||||
|
- "../packages:/srv/packages"
|
||||||
|
- "../tls:/srv/tls"
|
||||||
|
restart: "no"
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
apple_venv:
|
||||||
|
apple_node_modules:
|
||||||
16
services/apple/iOS (App)/AppDelegate.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// AppDelegate.h
|
||||||
|
// iOS (App)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 21/08/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@class HPAuthFlow;
|
||||||
|
|
||||||
|
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||||
|
|
||||||
|
@property (strong, nonnull) HPAuthFlow *authFlow;
|
||||||
|
|
||||||
|
@end
|
||||||
24
services/apple/iOS (App)/AppDelegate.m
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// AppDelegate.m
|
||||||
|
// iOS (App)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 21/08/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
|
||||||
|
#import "HPAuthFlow.h"
|
||||||
|
|
||||||
|
@implementation AppDelegate
|
||||||
|
|
||||||
|
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||||
|
self.authFlow = [[HPAuthFlow alloc] init];
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
|
||||||
|
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// AuthorizationProgressViewController.h
|
||||||
|
// HotPocket (iOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 25/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface AuthorizationProgressViewController : UIViewController
|
||||||
|
|
||||||
|
@property IBOutlet UIActivityIndicatorView *progressIndicator;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// AuthorizationProgressViewController.m
|
||||||
|
// HotPocket (iOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 25/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AuthorizationProgressViewController.h"
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "HPCredentialsHelper.h"
|
||||||
|
|
||||||
|
@interface AuthorizationProgressViewController (AuthorizationProgressViewControllerPrivate)
|
||||||
|
|
||||||
|
#pragma mark - Private interface
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AuthorizationProgressViewController
|
||||||
|
|
||||||
|
#pragma mark - View lifecycle
|
||||||
|
|
||||||
|
-(void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)viewWillAppear:(BOOL)animated {
|
||||||
|
[super viewWillAppear:animated];
|
||||||
|
[self.progressIndicator startAnimating];
|
||||||
|
|
||||||
|
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAuthFlowDidFinish:) name:@"AuthFlowDidFinish" object:appDelegate.authFlow];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)viewWillDisappear:(BOOL)animated {
|
||||||
|
[super viewWillDisappear:animated];
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)viewDidDisappear:(BOOL)animated {
|
||||||
|
[super viewDidDisappear:animated];
|
||||||
|
[self.progressIndicator stopAnimating];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Notification handlers
|
||||||
|
|
||||||
|
-(void)onAuthFlowDidFinish:(NSNotification *)notification {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
#ifdef DEBUG
|
||||||
|
NSLog(@"-[AuthorizationViewController onAuthFlowDidFinish:] notification=`%@`", notification);
|
||||||
|
#endif
|
||||||
|
HPCredentials *credentials = [[HPCredentialsHelper sharedHelper] getCredentials];
|
||||||
|
|
||||||
|
if (credentials.usable == NO) {
|
||||||
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Oops!", @"Oops!")
|
||||||
|
message:NSLocalizedString(@"HotPocket couldn't complete this operation.", @"HotPocket couldn't complete this operation.")
|
||||||
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
|
|
||||||
|
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Oh well", @"Oh well")
|
||||||
|
style:UIAlertActionStyleDefault
|
||||||
|
handler:^(UIAlertAction *action) {
|
||||||
|
[alert dismissViewControllerAnimated:YES completion:^{
|
||||||
|
[self.navigationController popViewControllerAnimated:YES];
|
||||||
|
}];
|
||||||
|
}]];
|
||||||
|
|
||||||
|
[self presentViewController:alert animated:YES completion:nil];
|
||||||
|
} else {
|
||||||
|
[self.navigationController popToRootViewControllerAnimated:YES];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
22
services/apple/iOS (App)/AuthorizationViewController.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// AuthorizationViewController.h
|
||||||
|
// HotPocket (iOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 25/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface AuthorizationViewController : UIViewController
|
||||||
|
|
||||||
|
@property UIImageView *invalidURLWarningView;
|
||||||
|
|
||||||
|
@property IBOutlet UITextField *instanceURLField;
|
||||||
|
|
||||||
|
-(IBAction)doStartAuthorizationFlow:(id)sender;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
104
services/apple/iOS (App)/AuthorizationViewController.m
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
//
|
||||||
|
// AuthorizationViewController.m
|
||||||
|
// HotPocket (iOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 25/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AuthorizationViewController.h"
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "AuthorizationProgressViewController.h"
|
||||||
|
#import "HPAuthFlow.h"
|
||||||
|
#import "HPCredentialsHelper.h"
|
||||||
|
#import "MainViewController.h"
|
||||||
|
#import "NSURL+HotPocketExtensions.h"
|
||||||
|
|
||||||
|
@interface AuthorizationViewController (AuthorizationViewControllerPrivate)
|
||||||
|
|
||||||
|
#pragma mark - Private interface
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AuthorizationViewController
|
||||||
|
|
||||||
|
#pragma mark - View lifecycle
|
||||||
|
|
||||||
|
-(void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
self.invalidURLWarningView = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"exclamationmark.circle.fill"]];
|
||||||
|
self.invalidURLWarningView.contentMode = UIViewContentModeScaleAspectFit;
|
||||||
|
self.invalidURLWarningView.frame = CGRectMake(0, 0, 16, 16);
|
||||||
|
self.invalidURLWarningView.tintColor = [UIColor colorNamed:@"WarningColor"];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)viewWillAppear:(BOOL)animated {
|
||||||
|
[super viewWillAppear:animated];
|
||||||
|
|
||||||
|
self.instanceURLField.rightView = self.invalidURLWarningView;
|
||||||
|
self.instanceURLField.rightViewMode = UITextFieldViewModeNever;
|
||||||
|
|
||||||
|
[self.instanceURLField addTarget:self action:@selector(onInstanceURLFieldChanged:) forControlEvents:UIControlEventEditingChanged];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Actions
|
||||||
|
|
||||||
|
-(IBAction)doStartAuthorizationFlow:(id)sender {
|
||||||
|
#ifdef DEBUG
|
||||||
|
NSLog(@"-[AuthorizationViewController doStartAuthorizationFlow:] instanceURL=`%@`", self.instanceURLField.text);
|
||||||
|
#endif
|
||||||
|
if (!self.instanceURLField.text) {
|
||||||
|
// ???
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURL *instanceURL = [NSURL URLWithString:self.instanceURLField.text];
|
||||||
|
if (instanceURL.isUsableInHotPocket == NO) {
|
||||||
|
// ???
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIApplication *application = [UIApplication sharedApplication];
|
||||||
|
AppDelegate *appDeleate = [application delegate];
|
||||||
|
appDeleate.authFlow.baseURL = instanceURL;
|
||||||
|
|
||||||
|
NSURL *authURL = [appDeleate.authFlow start];
|
||||||
|
if (authURL == nil) {
|
||||||
|
// ???
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([application canOpenURL:authURL] == YES) {
|
||||||
|
[application openURL:authURL options:@{} completionHandler:^(BOOL result) {
|
||||||
|
if (result == YES) {
|
||||||
|
AuthorizationProgressViewController *authorizationProgressViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"AuthorizationProgressViewController"];
|
||||||
|
[self.navigationController pushViewController:authorizationProgressViewController animated:YES];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Event handlers
|
||||||
|
|
||||||
|
-(void)onInstanceURLFieldChanged:(UITextField *)sender {
|
||||||
|
sender.rightViewMode = UITextFieldViewModeNever;
|
||||||
|
|
||||||
|
if (!sender.text || [@"" isEqualToString:sender.text]) {
|
||||||
|
sender.rightViewMode = UITextFieldViewModeAlways;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURL *url = [NSURL URLWithString:sender.text];
|
||||||
|
if (url == nil) {
|
||||||
|
sender.rightViewMode = UITextFieldViewModeAlways;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.isUsableInHotPocket == NO) {
|
||||||
|
sender.rightViewMode = UITextFieldViewModeAlways;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
54
services/apple/iOS (App)/Base.lproj/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24127" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
|
||||||
|
<capability name="Image references" minToolsVersion="12.0"/>
|
||||||
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="H2q-Qq-Nf1">
|
||||||
|
<rect key="frame" x="16" y="344" width="361" height="165"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6HG-Um-bch">
|
||||||
|
<rect key="frame" x="116" y="0.0" width="128" height="128"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<imageReference key="image" image="icon-mac-384.png"/>
|
||||||
|
</imageView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="HotPocket by BTHLabs" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="v6f-dw-6WO">
|
||||||
|
<rect key="frame" x="0.0" y="136" width="361" height="29"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||||
|
<color key="backgroundColor" name="BackgroundColor"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="52.671755725190835" y="374.64788732394368"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="icon-mac-384.png" width="384" height="384"/>
|
||||||
|
<namedColor name="BackgroundColor">
|
||||||
|
<color red="0.12941176470588237" green="0.14509803921568629" blue="0.16078431372549021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
252
services/apple/iOS (App)/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24127" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="7Sa-RR-xgc">
|
||||||
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
|
||||||
|
<capability name="Image references" minToolsVersion="12.0"/>
|
||||||
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Main View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController storyboardIdentifier="MainViewController" id="BYZ-38-t0r" customClass="MainViewController" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Tdb-RK-EKV">
|
||||||
|
<rect key="frame" x="20" y="96" width="374" height="165"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="57V-kg-4Nx">
|
||||||
|
<rect key="frame" x="122" y="0.0" width="128" height="128"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<imageReference key="image" image="icon-mac-384.png"/>
|
||||||
|
</imageView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="HotPocket by BTHLabs" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cP6-uT-Hh4">
|
||||||
|
<rect key="frame" x="0.0" y="136" width="374" height="29"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Safari and Share Extensions are installed." textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ltc-Em-W9y" customClass="MultilineLabel">
|
||||||
|
<rect key="frame" x="20" y="306" width="374" height="42"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Instance URL" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GYw-2b-qGS">
|
||||||
|
<rect key="frame" x="20" y="356" width="374" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OPO-AY-zgd">
|
||||||
|
<rect key="frame" x="20" y="385" width="374" height="35"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||||
|
<buttonConfiguration key="configuration" style="gray" title="DO NOT LOCALIZE">
|
||||||
|
<color key="baseForegroundColor" name="SecondaryColor"/>
|
||||||
|
</buttonConfiguration>
|
||||||
|
<connections>
|
||||||
|
<action selector="doOpenInstanceURL:" destination="BYZ-38-t0r" eventType="primaryActionTriggered" id="5SW-3o-wiJ"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="HotPocket is configured and ready." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0NJ-Zp-2hC">
|
||||||
|
<rect key="frame" x="20" y="277" width="374" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wQZ-n6-b0o">
|
||||||
|
<rect key="frame" x="161" y="428" width="92" height="35"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<buttonConfiguration key="configuration" style="gray" title="Log out">
|
||||||
|
<color key="baseForegroundColor" name="DangerColor"/>
|
||||||
|
</buttonConfiguration>
|
||||||
|
<connections>
|
||||||
|
<action selector="doLogOut:" destination="BYZ-38-t0r" eventType="primaryActionTriggered" id="iq7-wK-GMu"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||||
|
<color key="backgroundColor" name="BackgroundColor"/>
|
||||||
|
<color key="tintColor" name="AccentColor"/>
|
||||||
|
</view>
|
||||||
|
<navigationItem key="navigationItem" id="w8s-f0-7E0"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="instanceURLButton" destination="OPO-AY-zgd" id="1Wr-H9-eZ6"/>
|
||||||
|
<outlet property="logoutButton" destination="wQZ-n6-b0o" id="vco-vP-zvy"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="962.31884057971024" y="375"/>
|
||||||
|
</scene>
|
||||||
|
<!--Authorization View Controller-->
|
||||||
|
<scene sceneID="zfn-5m-i4Y">
|
||||||
|
<objects>
|
||||||
|
<viewController storyboardIdentifier="AuthorizationViewController" id="1Il-xJ-X5Y" customClass="AuthorizationViewController" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="gKn-cL-a2b">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Kkk-cr-Leu">
|
||||||
|
<rect key="frame" x="20" y="96" width="374" height="165"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YXT-lU-mHV">
|
||||||
|
<rect key="frame" x="122" y="0.0" width="128" height="128"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<imageReference key="image" image="icon-mac-384.png"/>
|
||||||
|
</imageView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="HotPocket by BTHLabs" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hhk-23-s8H">
|
||||||
|
<rect key="frame" x="0.0" y="136" width="374" height="29"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Instance URL" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="alG-Ve-nxN">
|
||||||
|
<rect key="frame" x="20" y="277" width="374" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="248" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="v5s-Uh-qWU" customClass="InstanceURLField">
|
||||||
|
<rect key="frame" x="20" y="306" width="374" height="34"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
|
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="URL" returnKeyType="go" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no" textContentType="url"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="doStartAuthorizationFlow:" destination="1Il-xJ-X5Y" eventType="primaryActionTriggered" id="Rd9-1f-N6Z"/>
|
||||||
|
</connections>
|
||||||
|
</textField>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Enter the URL to your HotPocket instance, e.g. https://my.hotpocket.app" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tn1-fl-daL" customClass="MultilineLabel">
|
||||||
|
<rect key="frame" x="20" y="348" width="374" height="64"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eKt-N1-DEJ">
|
||||||
|
<rect key="frame" x="20" y="428" width="374" height="35"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<state key="normal" title="Button"/>
|
||||||
|
<buttonConfiguration key="configuration" style="filled" title="Continue"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="doStartAuthorizationFlow:" destination="1Il-xJ-X5Y" eventType="primaryActionTriggered" id="U0V-Pp-M2x"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="dL2-4T-yXY"/>
|
||||||
|
<color key="backgroundColor" name="BackgroundColor"/>
|
||||||
|
<color key="tintColor" name="AccentColor"/>
|
||||||
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="instanceURLField" destination="v5s-Uh-qWU" id="hRQ-r8-3Dz"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="m6b-Bm-Ty7" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="1726.0869565217392" y="375"/>
|
||||||
|
</scene>
|
||||||
|
<!--Navigation Controller-->
|
||||||
|
<scene sceneID="zFJ-kU-27j">
|
||||||
|
<objects>
|
||||||
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="7Sa-RR-xgc" sceneMemberID="viewController">
|
||||||
|
<toolbarItems/>
|
||||||
|
<navigationBar key="navigationBar" contentMode="scaleToFill" id="PrZ-Cz-0b5">
|
||||||
|
<rect key="frame" x="0.0" y="96" width="414" height="54"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</navigationBar>
|
||||||
|
<nil name="viewControllers"/>
|
||||||
|
<connections>
|
||||||
|
<segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="7mY-Zh-QsC"/>
|
||||||
|
</connections>
|
||||||
|
</navigationController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="GIS-z2-loC" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="52.173913043478265" y="375"/>
|
||||||
|
</scene>
|
||||||
|
<!--Authorization Progress View Controller-->
|
||||||
|
<scene sceneID="689-0y-Gyr">
|
||||||
|
<objects>
|
||||||
|
<viewController storyboardIdentifier="AuthorizationProgressViewController" id="aiy-3v-nI7" customClass="AuthorizationProgressViewController" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="ljp-b5-lta">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wKr-WU-Iec">
|
||||||
|
<rect key="frame" x="20" y="96" width="374" height="165"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="v6u-sE-tzJ">
|
||||||
|
<rect key="frame" x="122" y="0.0" width="128" height="128"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<imageReference key="image" image="icon-mac-384.png"/>
|
||||||
|
</imageView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="HotPocket by BTHLabs" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="50v-cp-DGd">
|
||||||
|
<rect key="frame" x="0.0" y="136" width="374" height="29"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="DNy-gf-n60">
|
||||||
|
<rect key="frame" x="189" y="306" width="37" height="37"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
</activityIndicatorView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Awaiting authentication response..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qiJ-yx-nMd">
|
||||||
|
<rect key="frame" x="20" y="359" width="374" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="zyd-wv-1rn"/>
|
||||||
|
<color key="backgroundColor" name="BackgroundColor"/>
|
||||||
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="progressIndicator" destination="DNy-gf-n60" id="hJF-jc-ZJ0"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="N3D-cM-5Ro" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="2532" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<color key="tintColor" name="AccentColor"/>
|
||||||
|
<resources>
|
||||||
|
<image name="icon-mac-384.png" width="384" height="384"/>
|
||||||
|
<namedColor name="AccentColor">
|
||||||
|
<color red="0.10980392156862745" green="0.72941176470588232" blue="0.92941176470588238" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
<namedColor name="BackgroundColor">
|
||||||
|
<color red="0.12941176470588237" green="0.14509803921568629" blue="0.16078431372549021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
<namedColor name="DangerColor">
|
||||||
|
<color red="0.93300002813339233" green="0.3919999897480011" blue="0.46299999952316284" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
<namedColor name="SecondaryColor">
|
||||||
|
<color red="0.0" green="0.50980392156862742" blue="0.8666666666666667" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
15
services/apple/iOS (App)/HotPocket (iOS).entitlements
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?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>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.pl.bthlabs.HotPocket</string>
|
||||||
|
</array>
|
||||||
|
<key>keychain-access-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>$(AppIdentifierPrefix)pl.bthlabs.HotPocketShared</string>
|
||||||
|
<string>$(AppIdentifierPrefix)pl.bthlabs.HotPocket</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
53
services/apple/iOS (App)/Info.plist
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?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>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>CFBundleURLIconFile</key>
|
||||||
|
<string>icon-mac-384</string>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>HotPocketDesktopMac</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>hotpocket-mobile</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>HPAPIAccessTokenPlatform</key>
|
||||||
|
<string>iPhone</string>
|
||||||
|
<key>HPAuthFlowPostAuthenticateURLParts</key>
|
||||||
|
<dict>
|
||||||
|
<key>host</key>
|
||||||
|
<string>post-authenticate</string>
|
||||||
|
<key>scheme</key>
|
||||||
|
<string>hotpocket-mobile</string>
|
||||||
|
</dict>
|
||||||
|
<key>HPAuthFlowSource</key>
|
||||||
|
<string>HotPocketMobile</string>
|
||||||
|
<key>HPRPCClientOrigin</key>
|
||||||
|
<string>hotpocket-mobile://HPRPCClient</string>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>Default Configuration</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>SceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
16
services/apple/iOS (App)/InstanceURLField.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// InstanceURLField.h
|
||||||
|
// HotPocket (macOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 30/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface InstanceURLField : UITextField
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
21
services/apple/iOS (App)/InstanceURLField.m
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// InstanceURLField.m
|
||||||
|
// HotPocket (macOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 30/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "InstanceURLField.h"
|
||||||
|
|
||||||
|
@implementation InstanceURLField
|
||||||
|
|
||||||
|
-(CGRect)rightViewRectForBounds:(CGRect)bounds {
|
||||||
|
if (self.rightViewMode != UITextFieldViewModeNever) {
|
||||||
|
CGFloat offsetTop = (bounds.size.height - 16.0) / 2.0;
|
||||||
|
return CGRectMake(bounds.size.width - 16.0 - offsetTop, offsetTop, 16.0, 16.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CGRectNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
22
services/apple/iOS (App)/MainViewController.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// MainViewController.h
|
||||||
|
// HotPocket (iOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 25/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MainViewController : UIViewController
|
||||||
|
|
||||||
|
@property IBOutlet UIButton *instanceURLButton;
|
||||||
|
@property IBOutlet UIButton *logoutButton;
|
||||||
|
|
||||||
|
-(IBAction)doOpenInstanceURL:(id)sender;
|
||||||
|
-(IBAction)doLogOut:(id)sender;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
77
services/apple/iOS (App)/MainViewController.m
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
//
|
||||||
|
// MainViewController.m
|
||||||
|
// HotPocket (iOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 25/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MainViewController.h"
|
||||||
|
|
||||||
|
#import "HPCredentialsHelper.h"
|
||||||
|
|
||||||
|
#import "AuthorizationViewController.h"
|
||||||
|
|
||||||
|
@interface MainViewController (MainViewControllerPrivate)
|
||||||
|
|
||||||
|
#pragma mark - Private interface
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MainViewController
|
||||||
|
|
||||||
|
#pragma mark - View lifecycle
|
||||||
|
|
||||||
|
-(void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
[self.instanceURLButton setTitle:@"" forState:UIControlStateNormal];
|
||||||
|
self.instanceURLButton.enabled = NO;
|
||||||
|
|
||||||
|
self.logoutButton.enabled = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)viewWillAppear:(BOOL)animated {
|
||||||
|
[super viewWillAppear:animated];
|
||||||
|
[self.navigationController setNavigationBarHidden:YES animated:NO];
|
||||||
|
|
||||||
|
HPCredentials *credentials = [[HPCredentialsHelper sharedHelper] getCredentials];
|
||||||
|
if (credentials.usable == NO) {
|
||||||
|
AuthorizationViewController *authorizationViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"AuthorizationViewController"];
|
||||||
|
[self.navigationController pushViewController:authorizationViewController animated:NO];
|
||||||
|
} else {
|
||||||
|
[self.instanceURLButton setTitle:credentials.baseURL forState:UIControlStateNormal];
|
||||||
|
self.instanceURLButton.enabled = YES;
|
||||||
|
|
||||||
|
self.logoutButton.enabled = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *instanceURLText = @"";
|
||||||
|
if (credentials.baseURL != nil) {
|
||||||
|
instanceURLText = credentials.baseURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.instanceURLButton.titleLabel.text = instanceURLText;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Actions
|
||||||
|
|
||||||
|
-(IBAction)doOpenInstanceURL:(id)sender {
|
||||||
|
HPCredentials *credentials = [[HPCredentialsHelper sharedHelper] getCredentials];
|
||||||
|
if (credentials.usable == YES) {
|
||||||
|
NSURL *instanceURL = [NSURL URLWithString:credentials.baseURL];
|
||||||
|
|
||||||
|
UIApplication *application = [UIApplication sharedApplication];
|
||||||
|
if ([application canOpenURL:instanceURL] == YES) {
|
||||||
|
[application openURL:instanceURL options:@{} completionHandler:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(IBAction)doLogOut:(id)sender {
|
||||||
|
[[HPCredentialsHelper sharedHelper] clearCredentials];
|
||||||
|
|
||||||
|
AuthorizationViewController *authorizationViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"AuthorizationViewController"];
|
||||||
|
[self.navigationController pushViewController:authorizationViewController animated:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
16
services/apple/iOS (App)/MultilineLabel.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// MultilineLabel.h
|
||||||
|
// HotPocket (iOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 25/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface MultilineLabel : UILabel
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
24
services/apple/iOS (App)/MultilineLabel.m
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// MultilineLabel.m
|
||||||
|
// HotPocket (iOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 25/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MultilineLabel.h"
|
||||||
|
|
||||||
|
@implementation MultilineLabel
|
||||||
|
|
||||||
|
-(void)drawTextInRect:(CGRect)rect {
|
||||||
|
if (!self.text) {
|
||||||
|
[super drawTextInRect:rect];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGSize textSize = [self sizeThatFits:CGSizeMake(rect.size.width, CGFLOAT_MAX)];
|
||||||
|
CGRect textRect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, MIN(rect.size.height, textSize.height));
|
||||||
|
|
||||||
|
[super drawTextInRect:textRect];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
14
services/apple/iOS (App)/SceneDelegate.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// SceneDelegate.h
|
||||||
|
// iOS (App)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 21/08/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
|
||||||
|
|
||||||
|
@property (strong, nonatomic) UIWindow * window;
|
||||||
|
|
||||||
|
@end
|
||||||
30
services/apple/iOS (App)/SceneDelegate.m
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// SceneDelegate.m
|
||||||
|
// iOS (App)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 21/08/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SceneDelegate.h"
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "HPAuthFlow.h"
|
||||||
|
|
||||||
|
@implementation SceneDelegate
|
||||||
|
|
||||||
|
-(void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
|
||||||
|
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
|
||||||
|
|
||||||
|
for (UIOpenURLContext *context in URLContexts) {
|
||||||
|
NSURL *url = context.URL;
|
||||||
|
HPAuthParams *receivedAuthParams = [appDelegate.authFlow handlePostAuthenticateURL:url];
|
||||||
|
|
||||||
|
if (receivedAuthParams == nil) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[appDelegate.authFlow handleAuthParams:receivedAuthParams];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
18
services/apple/iOS (App)/main.m
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// main.m
|
||||||
|
// iOS (App)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 21/08/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
|
||||||
|
int main(int argc, char * argv[]) {
|
||||||
|
NSString *appDelegateClassName;
|
||||||
|
@autoreleasepool {
|
||||||
|
// Setup code that might create autoreleased objects goes here.
|
||||||
|
appDelegateClassName = NSStringFromClass([AppDelegate class]);
|
||||||
|
}
|
||||||
|
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
|
||||||
|
}
|
||||||
13
services/apple/iOS (Extension)/Info.plist
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?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>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.Safari.web-extension</string>
|
||||||
|
<key>NSExtensionPrincipalClass</key>
|
||||||
|
<string>SafariWebExtensionHandler</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24127" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
|
||||||
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
|
||||||
|
<capability name="Image references" minToolsVersion="12.0"/>
|
||||||
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Share View Controller-->
|
||||||
|
<scene sceneID="ceB-am-kn3">
|
||||||
|
<objects>
|
||||||
|
<viewController id="j1y-V4-xli" customClass="ShareViewController" sceneMemberID="viewController">
|
||||||
|
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="oWQ-SX-fOF">
|
||||||
|
<rect key="frame" x="20" y="118" width="353" height="165"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OAy-89-4VU">
|
||||||
|
<rect key="frame" x="111" y="0.0" width="127" height="128"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<imageReference key="image" image="icon-mac-384.png"/>
|
||||||
|
</imageView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="HotPocket by BTHLabs" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ern-ld-Ivg">
|
||||||
|
<rect key="frame" x="-2" y="136" width="355" height="29"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zdu-vr-R6t" userLabel="Saving View">
|
||||||
|
<rect key="frame" x="20" y="299" width="353" height="142"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="1HO-hL-WcQ">
|
||||||
|
<rect key="frame" x="158" y="6" width="37" height="37"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
</activityIndicatorView>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CqC-1V-R49">
|
||||||
|
<rect key="frame" x="138" y="107" width="77" height="35"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<state key="normal" title="Button"/>
|
||||||
|
<buttonConfiguration key="configuration" style="gray" title="Cancel">
|
||||||
|
<color key="baseForegroundColor" name="DangerColor"/>
|
||||||
|
</buttonConfiguration>
|
||||||
|
<connections>
|
||||||
|
<action selector="doCancel:" destination="j1y-V4-xli" eventType="primaryActionTriggered" id="EvP-s3-QOt"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9Dr-s7-Kqv" userLabel="Done View">
|
||||||
|
<rect key="frame" x="20" y="299" width="353" height="142"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="checkmark.circle.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="0oo-rV-Vfd">
|
||||||
|
<rect key="frame" x="152" y="0.0" width="48" height="48"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<color key="tintColor" name="SuccessColor"/>
|
||||||
|
</imageView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Your link has been saved!" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QoY-OC-nmX" customClass="MultilineLabel">
|
||||||
|
<rect key="frame" x="0.0" y="57" width="353" height="42"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="AHS-eb-Mk8">
|
||||||
|
<rect key="frame" x="143" y="107" width="67" height="35"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<color key="tintColor" name="AccentColor"/>
|
||||||
|
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||||
|
<state key="normal" title="Close"/>
|
||||||
|
<buttonConfiguration key="configuration" style="tinted" title="Close"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="doClose:" destination="j1y-V4-xli" eventType="primaryActionTriggered" id="6GF-4h-9aj"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DLn-U6-UcA" userLabel="Error View">
|
||||||
|
<rect key="frame" x="20" y="299" width="353" height="142"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="multiply.circle.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="HUo-6S-xfQ">
|
||||||
|
<rect key="frame" x="152" y="0.0" width="48" height="48"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<color key="tintColor" name="DangerColor"/>
|
||||||
|
</imageView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="HotPocket couldn't complete this operation." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FFg-7U-S0i" customClass="MultilineLabel">
|
||||||
|
<rect key="frame" x="0.0" y="57" width="353" height="42"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gFP-1G-Hef">
|
||||||
|
<rect key="frame" x="143" y="107" width="67" height="35"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<color key="tintColor" name="AccentColor"/>
|
||||||
|
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||||
|
<state key="normal" title="Close"/>
|
||||||
|
<buttonConfiguration key="configuration" style="tinted" title="Close"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="doClose:" destination="j1y-V4-xli" eventType="primaryActionTriggered" id="Z85-xF-RxD"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eqK-cv-2mf" userLabel="Needs Setup View">
|
||||||
|
<rect key="frame" x="20" y="299" width="353" height="142"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="exclamationmark.circle.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="fpS-az-ps2">
|
||||||
|
<rect key="frame" x="152" y="0.0" width="48" height="48"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<color key="tintColor" name="WarningColor"/>
|
||||||
|
</imageView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Open the HotPocket App to set it up." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xjE-hq-bDA" customClass="MultilineLabel">
|
||||||
|
<rect key="frame" x="0.0" y="57" width="353" height="42"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="UJs-K5-SJe">
|
||||||
|
<rect key="frame" x="143" y="107" width="67" height="35"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<color key="tintColor" name="AccentColor"/>
|
||||||
|
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||||
|
<state key="normal" title="Close"/>
|
||||||
|
<buttonConfiguration key="configuration" style="tinted" title="Close"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="doClose:" destination="j1y-V4-xli" eventType="primaryActionTriggered" id="40D-av-JQe"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<view contentMode="scaleToFill" id="ckY-6z-0fe" userLabel="Unprocessable Entity VIew">
|
||||||
|
<rect key="frame" x="20" y="299" width="353" height="142"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="exclamationmark.circle.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="tMs-QS-o9E">
|
||||||
|
<rect key="frame" x="152" y="0.0" width="48" height="48"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<color key="tintColor" name="WarningColor"/>
|
||||||
|
</imageView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="This item couldn't be shared :(." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tz8-Hp-Vha" customClass="MultilineLabel">
|
||||||
|
<rect key="frame" x="20" y="57" width="313" height="42"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0WL-Hk-6Bk">
|
||||||
|
<rect key="frame" x="143" y="107" width="67" height="35"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<color key="tintColor" name="AccentColor"/>
|
||||||
|
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||||
|
<state key="normal" title="Close"/>
|
||||||
|
<buttonConfiguration key="configuration" style="tinted" title="Close"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="doClose:" destination="j1y-V4-xli" eventType="primaryActionTriggered" id="9pE-y5-xBp"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="GQa-Md-2MI"/>
|
||||||
|
</view>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xfn-uj-KNk" userLabel="uname Label">
|
||||||
|
<rect key="frame" x="20" y="811" width="353" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
|
||||||
|
<color key="backgroundColor" name="BackgroundColor"/>
|
||||||
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="doneView" destination="9Dr-s7-Kqv" id="Ote-sW-Adm"/>
|
||||||
|
<outlet property="errorView" destination="DLn-U6-UcA" id="cl1-I0-nY1"/>
|
||||||
|
<outlet property="needsSetupView" destination="eqK-cv-2mf" id="Vb3-Y3-8Y8"/>
|
||||||
|
<outlet property="progressIndicator" destination="1HO-hL-WcQ" id="1Qd-Gt-b3G"/>
|
||||||
|
<outlet property="savingView" destination="zdu-vr-R6t" id="svY-bA-Z9d"/>
|
||||||
|
<outlet property="unameLabel" destination="xfn-uj-KNk" id="TTp-PV-ttr"/>
|
||||||
|
<outlet property="unprocessableEntityView" destination="ckY-6z-0fe" id="owN-qJ-oBj"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="138.1679389312977" y="130.98591549295776"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="checkmark.circle.fill" catalog="system" width="128" height="123"/>
|
||||||
|
<image name="exclamationmark.circle.fill" catalog="system" width="128" height="123"/>
|
||||||
|
<image name="icon-mac-384.png" width="384" height="384"/>
|
||||||
|
<image name="multiply.circle.fill" catalog="system" width="128" height="123"/>
|
||||||
|
<namedColor name="AccentColor">
|
||||||
|
<color red="0.10980392156862745" green="0.72941176470588232" blue="0.92941176470588238" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
<namedColor name="BackgroundColor">
|
||||||
|
<color red="0.12941176470588237" green="0.14509803921568629" blue="0.16078431372549021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
<namedColor name="DangerColor">
|
||||||
|
<color red="0.93300002813339233" green="0.3919999897480011" blue="0.46299999952316284" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
<namedColor name="SuccessColor">
|
||||||
|
<color red="0.054901960784313725" green="0.65490196078431373" blue="0.40392156862745099" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
<namedColor name="WarningColor">
|
||||||
|
<color red="0.94509803921568625" green="0.58823529411764708" blue="0.1803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
29
services/apple/iOS (Share Extension)/Info.plist
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?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>HPAPIAccessTokenPlatform</key>
|
||||||
|
<string>iPhone</string>
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionAttributes</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionActivationRule</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionActivationSupportsText</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>NSExtensionJavaScriptPreprocessingFile</key>
|
||||||
|
<string>ShareExtensionHelper</string>
|
||||||
|
</dict>
|
||||||
|
<key>NSExtensionMainStoryboard</key>
|
||||||
|
<string>MainInterface</string>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.share-services</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
20
services/apple/iOS (Share Extension)/ShareExtensionHelper.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// ShareExtensionHelper.js
|
||||||
|
// HotPocket
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 26/09/2025.
|
||||||
|
//
|
||||||
|
var ShareExtensionHelper = function() {
|
||||||
|
// OMG I CAN'T BELIEVE I HAVE TO EMBED JS IN THE SHARE EXTENSION :D
|
||||||
|
};
|
||||||
|
|
||||||
|
ShareExtensionHelper.prototype = {
|
||||||
|
run: function(arguments) {
|
||||||
|
arguments.completionFunction({
|
||||||
|
'iHateComputers': true,
|
||||||
|
'url': document.location.href,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var ExtensionPreprocessingJS = new ShareExtensionHelper();
|
||||||
27
services/apple/iOS (Share Extension)/ShareViewController.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// ShareViewController.h
|
||||||
|
// iOS (Share Extension)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 25/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@class HPAPI;
|
||||||
|
|
||||||
|
@interface ShareViewController : UIViewController
|
||||||
|
|
||||||
|
@property HPAPI *api;
|
||||||
|
|
||||||
|
@property IBOutlet UIActivityIndicatorView *progressIndicator;
|
||||||
|
@property IBOutlet UIView *savingView;
|
||||||
|
@property IBOutlet UIView *needsSetupView;
|
||||||
|
@property IBOutlet UIView *doneView;
|
||||||
|
@property IBOutlet UIView *errorView;
|
||||||
|
@property IBOutlet UIView *unprocessableEntityView;
|
||||||
|
@property IBOutlet UILabel *unameLabel;
|
||||||
|
|
||||||
|
-(IBAction)doCancel:(id)sender;
|
||||||
|
-(IBAction)doClose:(id)sender;
|
||||||
|
|
||||||
|
@end
|
||||||
126
services/apple/iOS (Share Extension)/ShareViewController.m
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
//
|
||||||
|
// ShareViewController.m
|
||||||
|
// iOS (Share Extension)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 25/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||||
|
|
||||||
|
#import "ShareViewController.h"
|
||||||
|
|
||||||
|
#import "HPAPI.h"
|
||||||
|
#import "HPCredentialsHelper.h"
|
||||||
|
#import "HPShareExtensionHelper.h"
|
||||||
|
|
||||||
|
@implementation ShareViewController (ShareViewControllerPrivate)
|
||||||
|
|
||||||
|
#pragma mark - Private interface
|
||||||
|
|
||||||
|
-(void)saveURL:(NSURL *)url {
|
||||||
|
#ifdef DEBUG
|
||||||
|
NSLog(@"-[ShareViewController save:] url=`%@`", url);
|
||||||
|
#endif
|
||||||
|
BOOL callResult = [self.api save:url completionHandler:^(NSString * _Nullable callId, HPRPCCallResult * _Nullable result) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
self.savingView.hidden = YES;
|
||||||
|
|
||||||
|
if (result.error != nil) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
NSLog(@"-[ShareViewController resolveLinkAndSave] saveError=`%@`", result.error);
|
||||||
|
#endif
|
||||||
|
self.errorView.hidden = NO;
|
||||||
|
} else {
|
||||||
|
self.doneView.hidden = NO;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (callResult == NO) {
|
||||||
|
self.savingView.hidden = YES;
|
||||||
|
self.errorView.hidden = NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)resolveLinkAndSave {
|
||||||
|
HPShareExtensionHelper *helper = [[HPShareExtensionHelper alloc] initWithContext:self.extensionContext];
|
||||||
|
[helper processItems:^(NSURL *url) {
|
||||||
|
if (url == nil) {
|
||||||
|
self.savingView.hidden = YES;
|
||||||
|
self.unprocessableEntityView.hidden = NO;
|
||||||
|
} else {
|
||||||
|
[self saveURL:url];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation ShareViewController
|
||||||
|
|
||||||
|
#pragma mark - View lifecycle
|
||||||
|
|
||||||
|
-(void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
self.savingView.hidden = NO;
|
||||||
|
self.needsSetupView.hidden = YES;
|
||||||
|
self.doneView.hidden = YES;
|
||||||
|
self.errorView.hidden = YES;
|
||||||
|
self.unprocessableEntityView.hidden = YES;
|
||||||
|
|
||||||
|
NSBundle *mainBundle = [NSBundle mainBundle];
|
||||||
|
self.unameLabel.text = [NSString stringWithFormat:@"HotPocket v%@ (%@)", [mainBundle.infoDictionary valueForKey:@"CFBundleShortVersionString"], [mainBundle.infoDictionary valueForKey:@"CFBundleVersion"]];
|
||||||
|
|
||||||
|
self.api = [[HPAPI alloc] init];
|
||||||
|
if (self.api.rpcClient.hasCredentials == YES) {
|
||||||
|
self.savingView.hidden = NO;
|
||||||
|
self.needsSetupView.hidden = YES;
|
||||||
|
} else {
|
||||||
|
self.savingView.hidden = YES;
|
||||||
|
self.needsSetupView.hidden = NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)viewWillAppear:(BOOL)animated {
|
||||||
|
[super viewWillAppear:animated];
|
||||||
|
[self.progressIndicator startAnimating];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)viewWillDisappear:(BOOL)animated {
|
||||||
|
[super viewWillDisappear:animated];
|
||||||
|
[self.progressIndicator stopAnimating];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)viewDidAppear:(BOOL)animated {
|
||||||
|
[super viewDidAppear:animated];
|
||||||
|
|
||||||
|
[self.api checkAuth:^(BOOL authValid, NSError *error, NSString *callId) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
if (authValid == NO) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
NSLog(@"-[ShareViewController viewDidAppear:] checkAuthError=`%@`", error);
|
||||||
|
#endif
|
||||||
|
self.savingView.hidden = YES;
|
||||||
|
self.needsSetupView.hidden = NO;
|
||||||
|
} else {
|
||||||
|
[self resolveLinkAndSave];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Actions
|
||||||
|
|
||||||
|
-(IBAction)doCancel:(id)sender {
|
||||||
|
NSError *cancelError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil];
|
||||||
|
[self.extensionContext cancelRequestWithError:cancelError];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(IBAction)doClose:(id)sender {
|
||||||
|
NSExtensionItem *outputItem = [[NSExtensionItem alloc] init];
|
||||||
|
|
||||||
|
NSArray *outputItems = @[outputItem];
|
||||||
|
[self.extensionContext completeRequestReturningItems:outputItems completionHandler:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?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>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.pl.bthlabs.HotPocket</string>
|
||||||
|
</array>
|
||||||
|
<key>keychain-access-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>$(AppIdentifierPrefix)pl.bthlabs.HotPocketShared</string>
|
||||||
|
<string>$(AppIdentifierPrefix)pl.bthlabs.HotPocket.ShareExtension</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
6
services/apple/invoke.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
run:
|
||||||
|
echo: true
|
||||||
|
pty: true
|
||||||
|
files_to_version:
|
||||||
|
- "HotPocket.xcodeproj/project.pbxproj"
|
||||||
|
- "pyproject.toml"
|
||||||
16
services/apple/macOS (App)/AppDelegate.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// AppDelegate.h
|
||||||
|
// macOS (App)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 21/08/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
@class HPAuthFlow;
|
||||||
|
|
||||||
|
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||||
|
|
||||||
|
@property (strong, nonnull) HPAuthFlow *authFlow;
|
||||||
|
|
||||||
|
@end
|
||||||
38
services/apple/macOS (App)/AppDelegate.m
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// AppDelegate.m
|
||||||
|
// macOS (App)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 21/08/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
|
||||||
|
#import "HPAuthFlow.h"
|
||||||
|
#import "HPCredentialsHelper.h"
|
||||||
|
|
||||||
|
@implementation AppDelegate
|
||||||
|
|
||||||
|
-(void)applicationDidFinishLaunching:(NSNotification *)notification {
|
||||||
|
self.authFlow = [[HPAuthFlow alloc] init];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
|
||||||
|
HPAuthParams *receivedAuthParams = nil;
|
||||||
|
for (NSURL *url in urls) {
|
||||||
|
receivedAuthParams = [self.authFlow handlePostAuthenticateURL:url];
|
||||||
|
|
||||||
|
if (receivedAuthParams != nil) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivedAuthParams != nil) {
|
||||||
|
[self.authFlow handleAuthParams:receivedAuthParams];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// AuthorizationProgressViewController.h
|
||||||
|
// HotPocket (macOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 20/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface AuthorizationProgressViewController : NSViewController
|
||||||
|
|
||||||
|
@property IBOutlet NSProgressIndicator *progressIndicator;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// AuthorizationProgressViewController.m
|
||||||
|
// HotPocket (macOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 20/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AuthorizationProgressViewController.h"
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "AuthorizationViewController.h"
|
||||||
|
#import "HPCredentialsHelper.h"
|
||||||
|
#import "MainViewController.h"
|
||||||
|
#import "ReplaceAnimator.h"
|
||||||
|
|
||||||
|
@interface AuthorizationProgressViewController (AuthorizationProgressViewControllerPrivate)
|
||||||
|
|
||||||
|
#pragma mark - Private interface
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AuthorizationProgressViewController
|
||||||
|
|
||||||
|
#pragma mark - View lifecycle
|
||||||
|
|
||||||
|
-(void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)viewWillAppear {
|
||||||
|
AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAuthFlowDidFinish:) name:@"AuthFlowDidFinish" object:appDelegate.authFlow];
|
||||||
|
|
||||||
|
[self.progressIndicator startAnimation:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)viewDidDisappear {
|
||||||
|
[self.progressIndicator stopAnimation:self];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Notification handlers
|
||||||
|
|
||||||
|
-(void)onAuthFlowDidFinish:(NSNotification *)notification {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
HPCredentials *credentials = [[HPCredentialsHelper sharedHelper] getCredentials];
|
||||||
|
|
||||||
|
[[NSApplication sharedApplication] requestUserAttention:NSInformationalRequest];
|
||||||
|
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||||
|
|
||||||
|
if (credentials.usable == NO) {
|
||||||
|
NSAlert *alert = [[NSAlert alloc] init];
|
||||||
|
alert.alertStyle = NSAlertStyleCritical;
|
||||||
|
alert.messageText = NSLocalizedString(@"Oops!", @"Oops!");
|
||||||
|
alert.informativeText = NSLocalizedString(@"HotPocket couldn't complete this operation.", @"HotPocket couldn't complete this operation.");
|
||||||
|
[alert runModal];
|
||||||
|
|
||||||
|
AuthorizationViewController *authorizationViewController = [self.storyboard instantiateControllerWithIdentifier:@"AuthorizationViewController"];
|
||||||
|
[self presentViewController:authorizationViewController animator:[[ReplaceAnimator alloc] init]];
|
||||||
|
} else {
|
||||||
|
MainViewController *mainViewController = [self.storyboard instantiateControllerWithIdentifier:@"MainViewController"];
|
||||||
|
[self presentViewController:mainViewController animator:[[ReplaceAnimator alloc] init]];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
21
services/apple/macOS (App)/AuthorizationViewController.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// AuthorizationViewController.h
|
||||||
|
// HotPocket (macOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 20/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface AuthorizationViewController : NSViewController
|
||||||
|
|
||||||
|
@property (nullable) NSString *baseURL;
|
||||||
|
@property (nullable) NSString *authorizationSessionToken;
|
||||||
|
|
||||||
|
-(IBAction)doStartAuthorizationFlow:(id)sender;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
49
services/apple/macOS (App)/AuthorizationViewController.m
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// AuthorizationViewController.m
|
||||||
|
// HotPocket (macOS)
|
||||||
|
//
|
||||||
|
// Created by Tomek Wójcik on 20/09/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AuthorizationViewController.h"
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "HPAuthFlow.h"
|
||||||
|
#import "AuthorizationProgressViewController.h"
|
||||||
|
#import "ReplaceAnimator.h"
|
||||||
|
|
||||||
|
@interface AuthorizationViewController (AuthorizationViewControllerPrivate)
|
||||||
|
|
||||||
|
#pragma mark - Private interface
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AuthorizationViewController
|
||||||
|
|
||||||
|
#pragma mark - View lifecycle
|
||||||
|
|
||||||
|
-(void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
self.baseURL = nil;
|
||||||
|
self.authorizationSessionToken = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Actions
|
||||||
|
|
||||||
|
-(IBAction)doStartAuthorizationFlow:(id)sender {
|
||||||
|
AppDelegate *appDeleate = [[NSApplication sharedApplication] delegate];
|
||||||
|
appDeleate.authFlow.baseURL = [NSURL URLWithString:self.baseURL];
|
||||||
|
|
||||||
|
NSURL *authURL = [appDeleate.authFlow start];
|
||||||
|
if (authURL == nil) {
|
||||||
|
NSBeep();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorizationProgressViewController *authProgressViewController = [self.storyboard instantiateControllerWithIdentifier:@"AuthorizationProgressViewController"];
|
||||||
|
[self presentViewController:authProgressViewController animator:[[ReplaceAnimator alloc] init]];
|
||||||
|
|
||||||
|
[[NSWorkspace sharedWorkspace] openURL:authURL];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||