Compare commits

..

30 Commits

Author SHA1 Message Date
0ac2ca73ec Release v25.10.13
All checks were successful
CI / Checks (push) Successful in 3m58s
2025-10-13 21:46:18 +02:00
7b67a2f758 BTHLABS-62: Display progress in extension popup
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-10-13 18:48:00 +00:00
8b86145519 BTHLABS-61: Service layer refactoring
A journey to fix `ValidationError` in Pocket imports turned service
layer refactoring :D
2025-10-12 20:54:00 +02:00
ac7a8dd90e BTHLABS-0000: README.md fixes 2025-10-07 08:46:50 +02:00
6903b7f768 BTHLABS-0000: Nuked dotcom service
Moved to a separate repo
2025-10-07 08:45:10 +02:00
2e8b8d7330 BTHLABS-60: Appearance settings
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-10-07 04:42:58 +00:00
b4d5375954 BTHLABS-0000: Docker and CI tweaks
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-10-07 04:37:01 +00:00
3f3f90103c Release v25.10.4
All checks were successful
CI / Checks (push) Successful in 24m41s
2025-10-04 08:07:26 +02:00
8582c12ec7 BTHLABS-58: UI updates for Apple Apps 2025-10-04 08:07:00 +02:00
98b3798264 Release v25.10.3 2025-10-04 08:06:36 +02:00
6332a9cef9 BTHLABS-58: Tweaks and fixes
* Use explicit values to populate access token's platform in apps.
* Fix View Association layout.
* Web Extension popup layout rework.
2025-10-04 08:06:18 +02:00
efcce32b50 Release v25.10.2 2025-10-04 08:04:17 +02:00
0311a28571 BTHLABS-0000: Stop App Store Connect from nagging about encryption 2025-10-04 08:04:00 +02:00
cb001f7e91 BTHLABS-58: Fixing URL paths resolution to avoid double slashes 2025-10-04 08:03:42 +02:00
9ab2b304b8 Release v25.10.1 2025-10-04 08:03:07 +02:00
1fd4dd735d BTHLABS-58: Cleaning up and preparing Apple Apps for release 2025-10-04 08:02:49 +02:00
99e9226338 BTHLABS-58: Share Extension in Apple Apps 2025-10-04 08:02:13 +02:00
0c12f52569 Release v25.9.18
All checks were successful
CI / Checks (push) Successful in 18m22s
2025-09-18 20:43:05 +02:00
a6f01ba71e BTHLABS-0000: Fix bumping of the workspace 2025-09-18 20:42:49 +02:00
77526b1fae BTHLABS-0000: Allow bumping a single service from top-level tasks. 2025-09-18 20:41:04 +02:00
7c97445155 BTHLABS-0000: Fix a bug that prevented RPC-created saves from processing 2025-09-18 20:41:04 +02:00
a0f1a4ce80 Release v25.9.17
All checks were successful
CI / Checks (push) Successful in 16m16s
2025-09-17 20:38:59 +02:00
80fbbcddf3 BTHLABS-0000: bump-version task
Enough with manual version bumps :D
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-17 20:27:20 +02:00
0ab87e25a4 BTHLABS-0000: Add scope to PWA manifest so share sheet target. 2025-09-17 20:27:20 +02:00
495255206e BTHLABS-57: Pre-auth page in the extension 2025-09-17 20:27:20 +02:00
46254730bd BTHLABS-52: Firefox Desktop Extension 2025-09-17 20:27:08 +02:00
d1e60babf4 BTHLABS-56: _Copy share link_ button in view association page
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-15 06:28:38 +00:00
ab84f685c0 BTHLABS-51: Chrome Web Extension
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-14 06:34:43 +00:00
1a8c4bfebc BTHLABS-0000: eslint.config.js fixes and code cleanup 2025-09-13 09:05:29 +02:00
b15b48f702 BTHLABS-55: Inline create save form
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-13 06:56:44 +00:00
244 changed files with 8618 additions and 990 deletions

View 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

View File

@ -17,8 +17,35 @@ jobs:
steps:
- name: "Checkout the code"
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"
id: "setup-docker-buildx"
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"
uses: docker/build-push-action@v6
with:
@ -26,7 +53,10 @@ jobs:
context: "services/"
push: false
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"
uses: docker/build-push-action@v6
with:
@ -34,7 +64,10 @@ jobs:
context: "services/"
push: false
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"
uses: docker/build-push-action@v6
with:
@ -42,7 +75,10 @@ jobs:
context: "services/"
push: false
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"
uses: docker/build-push-action@v6
with:
@ -51,7 +87,10 @@ jobs:
target: "ci"
push: false
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"
uses: docker/build-push-action@v6
with:
@ -60,7 +99,10 @@ jobs:
target: "ci"
push: false
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:
@ -69,23 +111,91 @@ jobs:
target: "ci"
push: false
load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-local"
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"
if: "steps.prepare.conclusion == 'success'"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
run: |
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"
if: always()
if: "steps.prepare.conclusion == 'success'"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
run: |
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: always()
if: "steps.prepare.conclusion == 'success'"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
run: |
set -x
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm extension-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 \
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"
if: always()
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
run: |
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
View File

@ -1,2 +1,3 @@
.envrc*
.ipythonhome/
/docker-compose-ci-*.yaml

View File

@ -86,3 +86,5 @@ Licensed under terms of the MIT License
Pepper Hot Solid icon
Copyright (c) Icons8
Licensed under terms of the MIT License
Spinner Loader CSS from https://css-loaders.com/

View File

@ -66,7 +66,7 @@ $ docker run --rm -it \
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME=hotpocket \
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD=hotpocketm4st3r \
-p 8000:8000 \
docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.9.12-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
@ -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 `DJANGO_SETTINGS_MODULE` environment variable defaults to
`hotpocket_backend.settings.deployment.webapp`. This should be set to
`hotpocket_backend.settings.deployment.admin` in the Admin container.
`hotpocket_backend.settings.deployment.aio`.
**NOTE:** The command above specifies wildly insecure `SECRET_KEY` which is
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.
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
starting point for full-stack deployments.

View File

@ -1,6 +1,6 @@
services:
backend:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.9.12-01"
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.10.13-01"
environment:
HOTPOCKET_BACKEND_SECRET_KEY: "thisisntright"
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME: "hotpocket"

View File

@ -8,7 +8,7 @@ x-backend-environment: &x-backend-environment
services:
webapp:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.12-01"
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
environment:
<<: *x-backend-environment
HOTPOCKET_BACKEND_ALLOWED_HOSTS: "app.staging.hotpocket.bthlab.bthlabs.net"
@ -21,7 +21,7 @@ services:
restart: "unless-stopped"
admin:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.12-01"
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
environment:
<<: *x-backend-environment
HOTPOCKET_BACKEND_APP: "admin"
@ -35,7 +35,7 @@ services:
restart: "unless-stopped"
celery-worker:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.12-01"
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
command:
- "/srv/venv/bin/celery"
- "-A"
@ -57,7 +57,7 @@ services:
restart: "unless-stopped"
celery-beat:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.12-01"
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
command:
- "/srv/venv/bin/celery"
- "-A"

View File

@ -2,6 +2,7 @@
"group": {
"default": {
"targets": [
"apple-management",
"backend-management",
"caddy",
"extension-management",
@ -13,6 +14,28 @@
}
},
"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": {
"context": "services/",
"dockerfile": "backend/Dockerfile",

View File

@ -1,16 +1,17 @@
services:
postgres:
ports: []
ports: !override []
keycloak:
command: "echo 'NOOP'"
ports: []
ports: !override []
restart: "no"
rabbitmq:
ports: []
ports: !override []
include:
- path: "./services/backend/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"

View File

@ -6,6 +6,7 @@ include:
- path: "./services/backend/docker-compose.yaml"
- path: "./services/packages/docker-compose.yaml"
- path: "./services/extension/docker-compose.yaml"
- path: "./services/apple/docker-compose.yaml"
volumes: {}

View File

@ -1,6 +0,0 @@
{
"run": {
"echo": true,
"pty": true
}
}

8
invoke.yaml Normal file
View 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
View File

@ -9,6 +9,9 @@ python-versions = "^3.12"
files = []
develop = true
[package.dependencies]
invoke = "2.2.0"
[package.source]
type = "directory"
url = "services/packages/workspace_tools"

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "hotpocket-workspace"
version = "1.0.0"
version = "25.10.13"
description = "HotPocket Workspace"
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
license = "Apache-2.0"

View File

@ -1,5 +1,8 @@
.mypy_cache/
.pytest_cache/
_tmp/
apple/
apple/build/
apple/DerivedData/
backend/node_modules/
backend/ops/metal/
backend/hotpocket_backend/playground.py

View File

@ -90,3 +90,5 @@ 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
View 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/

View File

@ -7,11 +7,33 @@
objects = {
/* Begin PBXBuildFile section */
4C1159202E8B055F003B34AD /* Save to HotPocket.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4CBCEA4F2E81CB9500722009 /* Save to HotPocket.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4C2F0C692E851BBD0033F5C2 /* Save to HotPocket.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4C70F30D2E8869FB00320048 /* HPShareExtensionHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F30C2E8869FB00320048 /* HPShareExtensionHelper.m */; };
4C70F30E2E8869FB00320048 /* HPShareExtensionHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F30C2E8869FB00320048 /* HPShareExtensionHelper.m */; };
4C70F3152E886A8F00320048 /* HPSharedItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F3142E886A8F00320048 /* HPSharedItem.m */; };
4C70F3162E886A8F00320048 /* HPSharedItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F3142E886A8F00320048 /* HPSharedItem.m */; };
4C70F3192E886ADD00320048 /* HPSharedItemsContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F3182E886ADD00320048 /* HPSharedItemsContainer.m */; };
4C70F31A2E886ADD00320048 /* HPSharedItemsContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F3182E886ADD00320048 /* HPSharedItemsContainer.m */; };
4CABCAD62E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4CABCAE02E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
4C1159212E8B055F003B34AD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4CABCA922E56F0C800D8A354 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 4CBCEA4E2E81CB9500722009;
remoteInfo = "macOS (Share Extension)";
};
4C2F0C672E851BBD0033F5C2 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4CABCA922E56F0C800D8A354 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 4C2F0C5D2E851BBD0033F5C2;
remoteInfo = "iOS (Share Extension)";
};
4CABCAD72E56F0C900D8A354 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4CABCA922E56F0C800D8A354 /* Project object */;
@ -36,6 +58,7 @@
dstSubfolderSpec = 13;
files = (
4CABCAD62E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */,
4C2F0C692E851BBD0033F5C2 /* Save to HotPocket.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
@ -46,6 +69,7 @@
dstPath = "";
dstSubfolderSpec = 13;
files = (
4C1159202E8B055F003B34AD /* Save to HotPocket.appex in Embed Foundation Extensions */,
4CABCAE02E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
@ -54,22 +78,64 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Save to HotPocket.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
4C70F30B2E8869FB00320048 /* HPShareExtensionHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HPShareExtensionHelper.h; sourceTree = "<group>"; };
4C70F30C2E8869FB00320048 /* HPShareExtensionHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HPShareExtensionHelper.m; sourceTree = "<group>"; };
4C70F3132E886A8F00320048 /* HPSharedItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HPSharedItem.h; sourceTree = "<group>"; };
4C70F3142E886A8F00320048 /* HPSharedItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HPSharedItem.m; sourceTree = "<group>"; };
4C70F3172E886ADD00320048 /* HPSharedItemsContainer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HPSharedItemsContainer.h; sourceTree = "<group>"; };
4C70F3182E886ADD00320048 /* HPSharedItemsContainer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HPSharedItemsContainer.m; sourceTree = "<group>"; };
4CABCAB02E56F0C900D8A354 /* HotPocket.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HotPocket.app; sourceTree = BUILT_PRODUCTS_DIR; };
4CABCAC62E56F0C900D8A354 /* HotPocket.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HotPocket.app; sourceTree = BUILT_PRODUCTS_DIR; };
4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "HotPocket Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "HotPocket Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
4CBCEA4F2E81CB9500722009 /* Save to HotPocket.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Save to HotPocket.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
4C2F0C6D2E851BBD0033F5C2 /* Exceptions for "iOS (Share Extension)" folder in "iOS (Share Extension)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 4C2F0C5D2E851BBD0033F5C2 /* iOS (Share Extension) */;
};
4C2F0C6F2E851BF90033F5C2 /* Exceptions for "Shared (App)" folder in "iOS (Share Extension)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Assets.xcassets,
HPAPI.m,
HPCredentialsHelper.m,
HPRPCClient.m,
"NSURL+HotPocketExtensions.m",
"Resources/icon-mac-384.png",
);
target = 4C2F0C5D2E851BBD0033F5C2 /* iOS (Share Extension) */;
};
4C3B958C2E83C83A00F4F82C /* Exceptions for "macOS (App)" folder in "HotPocket (macOS)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 4CABCAC52E56F0C900D8A354 /* HotPocket (macOS) */;
};
4C7A01792E867D6200DEA460 /* Exceptions for "iOS (App)" folder in "iOS (Share Extension)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
MultilineLabel.m,
);
target = 4C2F0C5D2E851BBD0033F5C2 /* iOS (Share Extension) */;
};
4CABCB042E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (iOS)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
"/Localized: Resources/Main.html",
Assets.xcassets,
HPAPI.m,
HPAuthFlow.m,
HPCredentialsHelper.m,
HPRPCClient.m,
"NSURL+HotPocketExtensions.m",
"Resources/icon-mac-384.png",
Resources/Script.js,
Resources/Style.css,
ViewController.m,
);
target = 4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */;
};
@ -90,12 +156,13 @@
4CABCB0E2E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (macOS)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
"/Localized: Resources/Main.html",
Assets.xcassets,
HPAPI.m,
HPAuthFlow.m,
HPCredentialsHelper.m,
HPRPCClient.m,
"NSURL+HotPocketExtensions.m",
"Resources/icon-mac-384.png",
Resources/Script.js,
Resources/Style.css,
ViewController.m,
);
target = 4CABCAC52E56F0C900D8A354 /* HotPocket (macOS) */;
};
@ -114,6 +181,8 @@
"Resources/content-bundle.js",
Resources/images,
Resources/manifest.json,
Resources/preauth.html,
Resources/preauth.js,
SafariWebExtensionHandler.m,
);
target = 4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */;
@ -126,18 +195,49 @@
"Resources/content-bundle.js",
Resources/images,
Resources/manifest.json,
Resources/preauth.html,
Resources/preauth.js,
SafariWebExtensionHandler.m,
);
target = 4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */;
};
4CBCEA612E81CB9500722009 /* Exceptions for "macOS (Share Extension)" folder in "macOS (Share Extension)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 4CBCEA4E2E81CB9500722009 /* macOS (Share Extension) */;
};
4CBCEA632E81CBC800722009 /* Exceptions for "Shared (App)" folder in "macOS (Share Extension)" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Assets.xcassets,
HPAPI.m,
HPCredentialsHelper.m,
HPRPCClient.m,
"NSURL+HotPocketExtensions.m",
"Resources/icon-mac-384.png",
);
target = 4CBCEA4E2E81CB9500722009 /* macOS (Share Extension) */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
4C2F0C5F2E851BBD0033F5C2 /* iOS (Share Extension) */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4C2F0C6D2E851BBD0033F5C2 /* Exceptions for "iOS (Share Extension)" folder in "iOS (Share Extension)" target */,
);
path = "iOS (Share Extension)";
sourceTree = "<group>";
};
4CABCA962E56F0C800D8A354 /* Shared (App) */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4CABCB042E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (iOS)" target */,
4CABCB0E2E56F0C900D8A354 /* Exceptions for "Shared (App)" folder in "HotPocket (macOS)" target */,
4CBCEA632E81CBC800722009 /* Exceptions for "Shared (App)" folder in "macOS (Share Extension)" target */,
4C2F0C6F2E851BF90033F5C2 /* Exceptions for "Shared (App)" folder in "iOS (Share Extension)" target */,
);
path = "Shared (App)";
sourceTree = "<group>";
@ -159,12 +259,16 @@
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4CABCB0D2E56F0C900D8A354 /* Exceptions for "iOS (App)" folder in "HotPocket (iOS)" target */,
4C7A01792E867D6200DEA460 /* Exceptions for "iOS (App)" folder in "iOS (Share Extension)" target */,
);
path = "iOS (App)";
sourceTree = "<group>";
};
4CABCAC72E56F0C900D8A354 /* macOS (App) */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4C3B958C2E83C83A00F4F82C /* Exceptions for "macOS (App)" folder in "HotPocket (macOS)" target */,
);
path = "macOS (App)";
sourceTree = "<group>";
};
@ -184,9 +288,24 @@
path = "macOS (Extension)";
sourceTree = "<group>";
};
4CBCEA502E81CB9500722009 /* macOS (Share Extension) */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4CBCEA612E81CB9500722009 /* Exceptions for "macOS (Share Extension)" folder in "macOS (Share Extension)" target */,
);
path = "macOS (Share Extension)";
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
4C2F0C5B2E851BBD0033F5C2 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCAAD2E56F0C900D8A354 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -215,18 +334,49 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
4CBCEA4C2E81CB9500722009 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
4C11591F2E8B055F003B34AD /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
4C70F30A2E8869D200320048 /* Shared (Share Extension) */ = {
isa = PBXGroup;
children = (
4C70F30B2E8869FB00320048 /* HPShareExtensionHelper.h */,
4C70F30C2E8869FB00320048 /* HPShareExtensionHelper.m */,
4C70F3132E886A8F00320048 /* HPSharedItem.h */,
4C70F3142E886A8F00320048 /* HPSharedItem.m */,
4C70F3172E886ADD00320048 /* HPSharedItemsContainer.h */,
4C70F3182E886ADD00320048 /* HPSharedItemsContainer.m */,
);
path = "Shared (Share Extension)";
sourceTree = "<group>";
};
4CABCA912E56F0C800D8A354 = {
isa = PBXGroup;
children = (
4CABCA962E56F0C800D8A354 /* Shared (App) */,
4CABCAA02E56F0C900D8A354 /* Shared (Extension) */,
4C70F30A2E8869D200320048 /* Shared (Share Extension) */,
4CABCAB22E56F0C900D8A354 /* iOS (App) */,
4CABCAC72E56F0C900D8A354 /* macOS (App) */,
4CABCAD92E56F0C900D8A354 /* iOS (Extension) */,
4CABCAE32E56F0C900D8A354 /* macOS (Extension) */,
4CBCEA502E81CB9500722009 /* macOS (Share Extension) */,
4C2F0C5F2E851BBD0033F5C2 /* iOS (Share Extension) */,
4C11591F2E8B055F003B34AD /* Frameworks */,
4CABCAB12E56F0C900D8A354 /* Products */,
);
sourceTree = "<group>";
@ -238,6 +388,8 @@
4CABCAC62E56F0C900D8A354 /* HotPocket.app */,
4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */,
4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */,
4CBCEA4F2E81CB9500722009 /* Save to HotPocket.appex */,
4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket.appex */,
);
name = Products;
sourceTree = "<group>";
@ -245,6 +397,28 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
4C2F0C5D2E851BBD0033F5C2 /* iOS (Share Extension) */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4C2F0C6A2E851BBD0033F5C2 /* Build configuration list for PBXNativeTarget "iOS (Share Extension)" */;
buildPhases = (
4C2F0C5A2E851BBD0033F5C2 /* Sources */,
4C2F0C5B2E851BBD0033F5C2 /* Frameworks */,
4C2F0C5C2E851BBD0033F5C2 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
4C2F0C5F2E851BBD0033F5C2 /* iOS (Share Extension) */,
);
name = "iOS (Share Extension)";
packageProductDependencies = (
);
productName = "iOS (Share Extension)";
productReference = 4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket.appex */;
productType = "com.apple.product-type.app-extension";
};
4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4CABCB0A2E56F0C900D8A354 /* Build configuration list for PBXNativeTarget "HotPocket (iOS)" */;
@ -258,6 +432,7 @@
);
dependencies = (
4CABCAD82E56F0C900D8A354 /* PBXTargetDependency */,
4C2F0C682E851BBD0033F5C2 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
4CABCAB22E56F0C900D8A354 /* iOS (App) */,
@ -282,6 +457,7 @@
);
dependencies = (
4CABCAE22E56F0C900D8A354 /* PBXTargetDependency */,
4C1159222E8B055F003B34AD /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
4CABCAC72E56F0C900D8A354 /* macOS (App) */,
@ -337,6 +513,28 @@
productReference = 4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */;
productType = "com.apple.product-type.app-extension";
};
4CBCEA4E2E81CB9500722009 /* macOS (Share Extension) */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4CBCEA602E81CB9500722009 /* Build configuration list for PBXNativeTarget "macOS (Share Extension)" */;
buildPhases = (
4CBCEA4B2E81CB9500722009 /* Sources */,
4CBCEA4C2E81CB9500722009 /* Frameworks */,
4CBCEA4D2E81CB9500722009 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
4CBCEA502E81CB9500722009 /* macOS (Share Extension) */,
);
name = "macOS (Share Extension)";
packageProductDependencies = (
);
productName = "macOS (Share Extension)";
productReference = 4CBCEA4F2E81CB9500722009 /* Save to HotPocket.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -346,6 +544,9 @@
BuildIndependentTargetsInParallel = 1;
LastUpgradeCheck = 1640;
TargetAttributes = {
4C2F0C5D2E851BBD0033F5C2 = {
CreatedOnToolsVersion = 26.0;
};
4CABCAAF2E56F0C900D8A354 = {
CreatedOnToolsVersion = 16.4;
};
@ -358,6 +559,9 @@
4CABCADE2E56F0C900D8A354 = {
CreatedOnToolsVersion = 16.4;
};
4CBCEA4E2E81CB9500722009 = {
CreatedOnToolsVersion = 16.4;
};
};
};
buildConfigurationList = 4CABCA952E56F0C800D8A354 /* Build configuration list for PBXProject "HotPocket" */;
@ -378,11 +582,20 @@
4CABCAC52E56F0C900D8A354 /* HotPocket (macOS) */,
4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */,
4CABCADE2E56F0C900D8A354 /* HotPocket Extension (macOS) */,
4CBCEA4E2E81CB9500722009 /* macOS (Share Extension) */,
4C2F0C5D2E851BBD0033F5C2 /* iOS (Share Extension) */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
4C2F0C5C2E851BBD0033F5C2 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCAAE2E56F0C900D8A354 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -411,9 +624,26 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
4CBCEA4D2E81CB9500722009 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
4C2F0C5A2E851BBD0033F5C2 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4C70F30E2E8869FB00320048 /* HPShareExtensionHelper.m in Sources */,
4C70F3152E886A8F00320048 /* HPSharedItem.m in Sources */,
4C70F3192E886ADD00320048 /* HPSharedItemsContainer.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
4CABCAAC2E56F0C900D8A354 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -442,9 +672,29 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
4CBCEA4B2E81CB9500722009 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4C70F3162E886A8F00320048 /* HPSharedItem.m in Sources */,
4C70F30D2E8869FB00320048 /* HPShareExtensionHelper.m in Sources */,
4C70F31A2E886ADD00320048 /* HPSharedItemsContainer.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
4C1159222E8B055F003B34AD /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4CBCEA4E2E81CB9500722009 /* macOS (Share Extension) */;
targetProxy = 4C1159212E8B055F003B34AD /* PBXContainerItemProxy */;
};
4C2F0C682E851BBD0033F5C2 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4C2F0C5D2E851BBD0033F5C2 /* iOS (Share Extension) */;
targetProxy = 4C2F0C672E851BBD0033F5C2 /* PBXContainerItemProxy */;
};
4CABCAD82E56F0C900D8A354 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4CABCAD42E56F0C900D8A354 /* HotPocket Extension (iOS) */;
@ -458,15 +708,83 @@
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
4C2F0C6B2E851BBD0033F5C2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "iOS (Share Extension)/iOS (Share Extension).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Share Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Save to HotPocket";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 25.10.13;
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
PRODUCT_NAME = "Save to HotPocket";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
4C2F0C6C2E851BBD0033F5C2 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "iOS (Share Extension)/iOS (Share Extension).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Share Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Save to HotPocket";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 25.10.13;
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
PRODUCT_NAME = "Save to HotPocket";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
4CABCB062E56F0C900D8A354 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091201;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
@ -474,7 +792,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 25.9.12;
MARKETING_VERSION = 25.10.13;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@ -483,6 +801,10 @@
PRODUCT_NAME = "HotPocket Extension";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
@ -492,11 +814,12 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091201;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
@ -504,7 +827,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 25.9.12;
MARKETING_VERSION = 25.10.13;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@ -513,6 +836,10 @@
PRODUCT_NAME = "HotPocket Extension";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
@ -524,24 +851,29 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "iOS (App)/HotPocket (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091201;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (App)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 25.9.12;
MARKETING_VERSION = 25.10.13;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@ -565,24 +897,29 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "iOS (App)/HotPocket (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091201;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (App)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 25.9.12;
MARKETING_VERSION = 25.10.13;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@ -606,29 +943,31 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091201;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "macOS (Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 25.9.12;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.10.13;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
);
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.Extension;
PRODUCT_NAME = "HotPocket Extension";
REGISTER_APP_GROUPS = YES;
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
@ -641,27 +980,29 @@
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091201;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "macOS (Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 25.9.12;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.10.13;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
);
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.Extension;
PRODUCT_NAME = "HotPocket Extension";
REGISTER_APP_GROUPS = YES;
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
@ -674,13 +1015,15 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091201;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "macOS (App)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
INFOPLIST_KEY_NSMainStoryboardFile = Main;
@ -690,7 +1033,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.9.12;
MARKETING_VERSION = 25.10.13;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@ -713,11 +1056,13 @@
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025091201;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "macOS (App)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
INFOPLIST_KEY_NSMainStoryboardFile = Main;
@ -727,7 +1072,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.9.12;
MARKETING_VERSION = 25.10.13;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@ -855,9 +1200,78 @@
};
name = Release;
};
4CBCEA5E2E81CB9500722009 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "macOS (Share Extension)/macOS (Share Extension).entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "macOS (Share Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Save to HotPocket";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.10.13;
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
PRODUCT_NAME = "Save to HotPocket";
REGISTER_APP_GROUPS = YES;
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
};
name = Debug;
};
4CBCEA5F2E81CB9500722009 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "macOS (Share Extension)/macOS (Share Extension).entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2025101302;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "macOS (Share Extension)/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Save to HotPocket";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.10.13;
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
PRODUCT_NAME = "Save to HotPocket";
REGISTER_APP_GROUPS = YES;
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
4C2F0C6A2E851BBD0033F5C2 /* Build configuration list for PBXNativeTarget "iOS (Share Extension)" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4C2F0C6B2E851BBD0033F5C2 /* Debug */,
4C2F0C6C2E851BBD0033F5C2 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4CABCA952E56F0C800D8A354 /* Build configuration list for PBXProject "HotPocket" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@ -903,6 +1317,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4CBCEA602E81CB9500722009 /* Build configuration list for PBXNativeTarget "macOS (Share Extension)" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4CBCEA5E2E81CB9500722009 /* Debug */,
4CBCEA5F2E81CB9500722009 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 4CABCA922E56F0C800D8A354 /* Project object */;

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
View File

@ -0,0 +1,3 @@
# HotPocket by BTHLabs
This repository contains the _HotPocket Apple Integrations_ project.

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="../Style.css">
<script src="../Script.js" defer></script>
</head>
<body>
<img src="../icon-mac-384.png" width="128" height="128" alt="HotPocket Icon">
<p class="platform-ios">You can turn on Save to Hotpocket Safari extension in Settings.</p>
<p class="platform-mac state-unknown">You can turn on Save to Hotpocket extension in Safari Extensions preferences.</p>
<p class="platform-mac state-on">Save to Hotpocket extension is currently on. You can turn it off in Safari Extensions preferences.</p>
<p class="platform-mac state-off">Save to Hotpocket extension is currently off. You can turn it on in Safari Extensions preferences.</p>
<button class="platform-mac open-preferences">Quit and Open Safari Extensions Preferences…</button>
</body>
</html>

View File

@ -1,24 +0,0 @@
function show(platform, enabled, useSettingsInsteadOfPreferences) {
document.body.classList.add(`platform-${platform}`);
if (useSettingsInsteadOfPreferences) {
document.getElementsByClassName('platform-mac state-on')[0].innerText = "Save to Hotpocket extension is currently on. You can turn it off in the Extensions section of Safari Settings.";
document.getElementsByClassName('platform-mac state-off')[0].innerText = "Save to Hotpocket extension is currently off. You can turn it on in the Extensions section of Safari Settings.";
document.getElementsByClassName('platform-mac state-unknown')[0].innerText = "You can turn on Save to Hotpocket extension in the Extensions section of Safari Settings.";
document.getElementsByClassName('platform-mac open-preferences')[0].innerText = "Quit and Open Safari Settings…";
}
if (typeof enabled === "boolean") {
document.body.classList.toggle(`state-on`, enabled);
document.body.classList.toggle(`state-off`, !enabled);
} else {
document.body.classList.remove(`state-on`);
document.body.classList.remove(`state-off`);
}
}
function openPreferences() {
webkit.messageHandlers.controller.postMessage("open-preferences");
}
document.querySelector("button.open-preferences").addEventListener("click", openPreferences);

View File

@ -1,63 +0,0 @@
* {
-webkit-user-select: none;
-webkit-user-drag: none;
cursor: default;
}
:root {
color-scheme: light dark;
--spacing: 20px;
}
html {
height: 100%;
}
body {
background: #212529;
color: white;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: var(--spacing);
margin: 0 calc(var(--spacing) * 2);
height: 100%;
font: -apple-system-short-body;
text-align: center;
}
body:not(.platform-mac, .platform-ios) :is(.platform-mac, .platform-ios) {
display: none;
}
body.platform-ios .platform-mac {
display: none;
}
body.platform-mac .platform-ios {
display: none;
}
body.platform-ios .platform-mac {
display: none;
}
body:not(.state-on, .state-off) :is(.state-on, .state-off) {
display: none;
}
body.state-on :is(.state-off, .state-unknown) {
display: none;
}
body.state-off :is(.state-on, .state-unknown) {
display: none;
}
button {
font-size: 1em;
}

View File

@ -1,26 +0,0 @@
//
// ViewController.h
// Shared (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import <TargetConditionals.h>
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
typedef UIViewController PlatformViewController;
#elif TARGET_OS_OSX
#import <Cocoa/Cocoa.h>
typedef NSViewController PlatformViewController;
#endif
@interface ViewController : PlatformViewController
@end

View File

@ -1,76 +0,0 @@
//
// ViewController.m
// Shared (App)
//
// Created by Tomek Wójcik on 21/08/2025.
//
#import "ViewController.h"
#import <WebKit/WebKit.h>
#if TARGET_OS_OSX
#import <SafariServices/SafariServices.h>
#endif
static NSString * const extensionBundleIdentifier = @"pl.bthlabs.HotPocket.HotPocket.Extension";
@interface ViewController () <WKNavigationDelegate, WKScriptMessageHandler>
@property (nonatomic) IBOutlet WKWebView *webView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_webView.navigationDelegate = self;
#if TARGET_OS_IOS
_webView.scrollView.scrollEnabled = NO;
#endif
[_webView.configuration.userContentController addScriptMessageHandler:self name:@"controller"];
[_webView loadFileURL:[NSBundle.mainBundle URLForResource:@"Main" withExtension:@"html"] allowingReadAccessToURL:NSBundle.mainBundle.resourceURL];
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
#if TARGET_OS_IOS
[webView evaluateJavaScript:@"show('ios')" completionHandler:nil];
#elif TARGET_OS_OSX
[webView evaluateJavaScript:@"show('mac')" completionHandler:nil];
[SFSafariExtensionManager getStateOfSafariExtensionWithIdentifier:extensionBundleIdentifier completionHandler:^(SFSafariExtensionState *state, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!state) {
// Insert code to inform the user something went wrong.
return;
}
NSString *isExtensionEnabledAsString = state.isEnabled ? @"true" : @"false";
if (@available(macOS 13, *))
[webView evaluateJavaScript:[NSString stringWithFormat:@"show('mac', %@, true)", isExtensionEnabledAsString] completionHandler:nil];
else
[webView evaluateJavaScript:[NSString stringWithFormat:@"show('mac', %@, false)", isExtensionEnabledAsString] completionHandler:nil];
});
}];
#endif
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
#if TARGET_OS_OSX
if (![message.body isEqualToString:@"open-preferences"])
return;
[SFSafariApplication showPreferencesForExtensionWithIdentifier:extensionBundleIdentifier completionHandler:^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[NSApp terminate:self];
});
}];
#endif
}
@end

View File

@ -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

View 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

View 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

View 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

View File

@ -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

View File

@ -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

View 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

View 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:

View File

@ -7,6 +7,10 @@
#import <UIKit/UIKit.h>
@class HPAuthFlow;
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonnull) HPAuthFlow *authFlow;
@end

View File

@ -7,14 +7,17 @@
#import "AppDelegate.h"
#import "HPAuthFlow.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.authFlow = [[HPAuthFlow alloc] init];
return YES;
}
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
-(UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}

View File

@ -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

View File

@ -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

View 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

View 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

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<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="23721"/>
<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"/>
@ -16,12 +16,25 @@
<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="131" y="363" width="128" height="128"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<imageReference key="image" image="LargeIcon"/>
<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"/>
@ -29,11 +42,11 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
<point key="canvasLocation" x="52.671755725190835" y="374.64788732394368"/>
</scene>
</scenes>
<resources>
<image name="LargeIcon" width="128" height="128"/>
<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>

View File

@ -1,46 +1,252 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<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="23721"/>
<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-->
<!--Main View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController">
<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>
<wkWebView contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RDB-ib-igF">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" name="BackgroundColor"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
<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="webView" destination="RDB-ib-igF" id="avx-RC-qRB"/>
<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="53" y="375"/>
<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>

View 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>

View File

@ -2,6 +2,34 @@
<!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>

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -7,6 +7,24 @@
#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

View File

@ -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>

View 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>

View 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();

View 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

View 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

View 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.ShareExtension</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,6 @@
run:
echo: true
pty: true
files_to_version:
- "HotPocket.xcodeproj/project.pbxproj"
- "pyproject.toml"

View File

@ -7,6 +7,10 @@
#import <Cocoa/Cocoa.h>
@class HPAuthFlow;
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (strong, nonnull) HPAuthFlow *authFlow;
@end

View File

@ -7,14 +7,32 @@
#import "AppDelegate.h"
#import "HPAuthFlow.h"
#import "HPCredentialsHelper.h"
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
// Override point for customization after application launch.
-(void)applicationDidFinishLaunching:(NSNotification *)notification {
self.authFlow = [[HPAuthFlow alloc] init];
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
-(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

View File

@ -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

View File

@ -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

View 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

View 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

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="24127" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23727"/>
<plugIn identifier="com.apple.WebKit2IBPlugin" version="23727"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24127"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -77,7 +77,7 @@
<scene sceneID="R2V-B0-nI4">
<objects>
<windowController showSeguePresentationStyle="single" id="B8D-0N-5wS" sceneMemberID="viewController">
<window key="window" title="HotPocket" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" animationBehavior="default" id="IQv-IB-iLA">
<window key="window" title="HotPocket" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" animationBehavior="default" toolbarStyle="expanded" id="IQv-IB-iLA">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenNone="YES"/>
<rect key="contentRect" x="196" y="240" width="425" height="325"/>
@ -87,38 +87,225 @@
</connections>
</window>
<connections>
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
<segue destination="r5D-xE-cNT" kind="relationship" relationship="window.shadowedContentViewController" id="j8b-cd-GSP"/>
</connections>
</windowController>
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="250"/>
</scene>
<!--View Controller-->
<!--Authorization View Controller-->
<scene sceneID="hIz-AP-VOD">
<objects>
<viewController id="XfG-lQ-9wD" customClass="ViewController" sceneMemberID="viewController">
<view key="view" appearanceType="darkAqua" id="m2S-Jp-Qdl">
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<viewController storyboardIdentifier="AuthorizationViewController" id="XfG-lQ-9wD" customClass="AuthorizationViewController" sceneMemberID="viewController">
<view key="view" id="m2S-Jp-Qdl">
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<wkWebView wantsLayer="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eOr-cG-IQY">
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7sM-F3-Zzf">
<rect key="frame" x="18" y="153" width="389" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="HotPocket Instance URL" id="XwM-DV-kei">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ygC-xe-m6y">
<rect key="frame" x="20" y="124" width="385" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" id="rHK-hP-yWO">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="XfG-lQ-9wD" name="value" keyPath="self.baseURL" id="OhE-52-yPd">
<dictionary key="options">
<bool key="NSContinuouslyUpdatesValue" value="YES"/>
</dictionary>
</binding>
</connections>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DIc-8O-uoQ">
<rect key="frame" x="18" y="68" width="389" height="48"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" selectable="YES" title="Enter the URL to your HotPocket instance, e.g. https://my.hotpocket.app" id="Y0q-a1-oBP">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PAH-U3-ltl">
<rect key="frame" x="180" y="221" width="64" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="icon-mac-384" id="jnV-K2-7gf"/>
</imageView>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2Zr-9i-XDS">
<rect key="frame" x="13" y="33" width="89" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Continue" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="HBR-3P-qCC">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<color key="contentTintColor" name="AccentColor"/>
<connections>
<action selector="doStartAuthorizationFlow:" target="XfG-lQ-9wD" id="AOi-Wt-gmL"/>
</connections>
</button>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mQc-Ea-NNN">
<rect key="frame" x="18" y="185" width="389" height="28"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="HotPocket by BTHLabs" id="NTZ-zl-yhk">
<font key="font" metaFont="system" size="24"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
</viewController>
</objects>
<point key="canvasLocation" x="606" y="1067"/>
</scene>
<!--Authorization Progress View Controller-->
<scene sceneID="HWJ-b5-BG6">
<objects>
<viewController storyboardIdentifier="AuthorizationProgressViewController" id="OX4-Oj-1cw" customClass="AuthorizationProgressViewController" sceneMemberID="viewController">
<view key="view" id="qln-dC-eog">
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="yRj-hC-QYS">
<rect key="frame" x="18" y="185" width="389" height="28"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="HotPocket by BTHLabs" id="F4l-2Z-D79">
<font key="font" metaFont="system" size="24"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ca6-br-wxp">
<rect key="frame" x="180" y="221" width="64" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="icon-mac-384" id="faZ-e6-z8R"/>
</imageView>
<progressIndicator fixedFrame="YES" maxValue="100" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="51a-ii-yug">
<rect key="frame" x="196" y="113" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
</progressIndicator>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="g9a-gR-c7o">
<rect key="frame" x="18" y="81" width="389" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Awaiting authorization response..." id="3oi-LK-vKv">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
<connections>
<outlet property="webView" destination="eOr-cG-IQY" id="GFe-mU-dBY"/>
<outlet property="progressIndicator" destination="51a-ii-yug" id="hWy-Hb-3pE"/>
</connections>
</viewController>
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<customObject id="Dav-PG-FiQ" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="655"/>
<point key="canvasLocation" x="605.5" y="654.5"/>
</scene>
<!--Main View Controller-->
<scene sceneID="UJA-e4-0rA">
<objects>
<viewController storyboardIdentifier="MainViewController" id="r5D-xE-cNT" customClass="MainViewController" sceneMemberID="viewController">
<view key="view" id="jVt-oL-KPZ">
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ybL-DV-U73">
<rect key="frame" x="180" y="221" width="64" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="icon-mac-384" id="fae-mz-0sj"/>
</imageView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T7q-KB-3Ut">
<rect key="frame" x="18" y="185" width="389" height="28"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="HotPocket by BTHLabs" id="r5O-Sk-IdK">
<font key="font" metaFont="system" size="24"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2h7-bN-dsa">
<rect key="frame" x="18" y="153" width="389" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" selectable="YES" title="HotPocket is configured and ready." id="5fh-mh-WR1">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uci-UC-wxo">
<rect key="frame" x="18" y="89" width="389" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Instance URL" id="azk-ea-KeN">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LR4-eJ-jlA">
<rect key="frame" x="13" y="30" width="80" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Log out" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="LDr-35-Wph">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<color key="contentTintColor" name="DangerColor"/>
<connections>
<action selector="doLogOut:" target="r5D-xE-cNT" id="BMt-Zp-8v5"/>
<binding destination="r5D-xE-cNT" name="enabled" keyPath="self.logoutButtonEnabled" id="gTs-BO-USz"/>
</connections>
</button>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8H3-oU-acU" customClass="LinkLabel">
<rect key="frame" x="18" y="65" width="389" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" allowsEditingTextAttributes="YES" id="EoA-mM-phM">
<font key="font" metaFont="system"/>
<color key="textColor" name="SecondaryColor"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9pl-Ap-yxc">
<rect key="frame" x="18" y="113" width="389" height="32"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" selectable="YES" title="Safari and Share Extensions are installed." id="dy7-bw-DYh">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
<connections>
<outlet property="instanceURLLabel" destination="8H3-oU-acU" id="EuY-xr-zar"/>
</connections>
</viewController>
<customObject id="dlo-Mj-lGf" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="74.5" y="654.5"/>
</scene>
</scenes>
<resources>
<image name="icon-mac-384" 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="DangerColor">
<color red="0.93333333333333335" green="0.39215686274509803" blue="0.46274509803921571" 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>

View File

@ -4,9 +4,18 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.pl.bthlabs.HotPocket</string>
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)pl.bthlabs.HotPocketShared</string>
<string>$(AppIdentifierPrefix)pl.bthlabs.HotPocket</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,34 @@
<?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-desktop</string>
</array>
</dict>
</array>
<key>HPAPIAccessTokenPlatform</key>
<string>macOS</string>
<key>HPAuthFlowPostAuthenticateURLParts</key>
<dict>
<key>host</key>
<string>post-authenticate</string>
<key>scheme</key>
<string>hotpocket-desktop</string>
</dict>
<key>HPAuthFlowSource</key>
<string>HotPocketDesktop</string>
<key>HPRPCClientOrigin</key>
<string>hotpocket-desktop://HPRPCClient</string>
</dict>
</plist>

View File

@ -0,0 +1,16 @@
//
// LinkLabel.h
// HotPocket (macOS)
//
// Created by Tomek Wójcik on 24/09/2025.
//
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface LinkLabel : NSTextField
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,24 @@
//
// LinkLabel.m
// HotPocket (macOS)
//
// Created by Tomek Wójcik on 24/09/2025.
//
#import "LinkLabel.h"
@implementation LinkLabel
-(void)awakeFromNib {
[super awakeFromNib];
self.allowsEditingTextAttributes = YES;
self.textColor = [NSColor colorNamed:@"SecondaryColor"];
self.selectable = YES;
}
-(void)resetCursorRects {
[super resetCursorRects];
[self addCursorRect:self.bounds cursor:NSCursor.pointingHandCursor];
}
@end

View File

@ -0,0 +1,22 @@
//
// MainViewController.h
// HotPocket (iOS)
//
// Created by Tomek Wójcik on 22/09/2025.
//
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface MainViewController : NSViewController
@property IBOutlet NSTextField *instanceURLLabel;
@property BOOL logoutButtonEnabled;
-(IBAction)doLogOut:(id)sender;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,65 @@
//
// MainViewController.m
// HotPocket (iOS)
//
// Created by Tomek Wójcik on 22/09/2025.
//
#import "MainViewController.h"
#import "HPCredentialsHelper.h"
#import "AuthorizationViewController.h"
#import "ReplaceAnimator.h"
@interface MainViewController (MainViewControllerPrivate)
#pragma mark - Private interface
@end
@implementation MainViewController
#pragma mark - View lifecycle
-(void)viewDidLoad {
[super viewDidLoad];
self.logoutButtonEnabled = NO;
}
-(void)viewDidAppear {
HPCredentials *credentials = [[HPCredentialsHelper sharedHelper] getCredentials];
if (credentials.usable == NO) {
AuthorizationViewController *authorizationViewController = [self.storyboard instantiateControllerWithIdentifier:@"AuthorizationViewController"];
[self presentViewController:authorizationViewController animator:[[ReplaceAnimator alloc] init]];
} else {
self.logoutButtonEnabled = YES;
}
NSString *instanceURLText = @"";
if (credentials.baseURL != nil) {
instanceURLText = credentials.baseURL;
}
NSMutableAttributedString *instanceURLValue = [[NSMutableAttributedString alloc] initWithString:instanceURLText];
if (credentials.baseURL != nil) {
[instanceURLValue addAttribute:NSLinkAttributeName
value:credentials.baseURL
range:NSMakeRange(0, instanceURLValue.length)];
}
[instanceURLValue addAttribute:NSUnderlineStyleAttributeName
value:[NSNumber numberWithInteger:NSUnderlineStyleSingle]
range:NSMakeRange(0, instanceURLValue.length)];
self.instanceURLLabel.attributedStringValue = instanceURLValue;
}
#pragma mark - Actions
-(IBAction)doLogOut:(id)sender {
[[HPCredentialsHelper sharedHelper] clearCredentials];
AuthorizationViewController *authorizationViewController = [self.storyboard instantiateControllerWithIdentifier:@"AuthorizationViewController"];
[self presentViewController:authorizationViewController animator:[[ReplaceAnimator alloc] init]];
}
@end

View File

@ -0,0 +1,16 @@
//
// ReplaceAnimator.h
// HotPocket (macOS)
//
// Created by Tomek Wójcik on 20/09/2025.
//
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface ReplaceAnimator : NSObject<NSViewControllerPresentationAnimator>
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,29 @@
//
// ReplaceAnimator.m
// HotPocket (macOS)
//
// Created by Tomek Wójcik on 20/09/2025.
//
#import "ReplaceAnimator.h"
@implementation ReplaceAnimator
-(void)animatePresentationOfViewController:(NSViewController *)viewController fromViewController:(NSViewController *)fromViewController {
NSView *container = fromViewController.view.superview;
if (container == nil) {
return;
}
[fromViewController.view removeFromSuperview];
[container addSubview:viewController.view];
viewController.view.frame = NSMakeRect(0, 0, viewController.view.frame.size.width, viewController.view.frame.size.height);
viewController.view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
}
-(void)animateDismissalOfViewController:(NSViewController *)viewController fromViewController:(NSViewController *)fromViewController {
[viewController.view removeFromSuperview];
}
@end

View File

@ -0,0 +1,19 @@
//
// WindowContentView.h
// HotPocket (macOS)
//
// Created by Tomek Wójcik on 30/09/2025.
//
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface WindowContentView : NSView
@property (strong) NSColor *darkBackgroundColor;
@property (strong) NSColor *lightBackgroundColor;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,42 @@
//
// WindowContentView.m
// HotPocket (macOS)
//
// Created by Tomek Wójcik on 30/09/2025.
//
#import "WindowContentView.h"
@implementation WindowContentView
-(void)awakeFromNib {
[super awakeFromNib];
self.darkBackgroundColor = [NSColor colorNamed:@"BackgroundColor"];
self.lightBackgroundColor = [NSColor windowBackgroundColor];
}
-(BOOL)isOpaque {
return YES;
}
-(void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
NSAppearance *appearance = self.effectiveAppearance;
NSAppearanceName bestMatch = [appearance bestMatchFromAppearancesWithNames:@[
NSAppearanceNameAqua,
NSAppearanceNameDarkAqua
]];
NSColor *backgroundColor = self.lightBackgroundColor;
if ([bestMatch isEqualToString:NSAppearanceNameDarkAqua] == YES) {
backgroundColor = self.darkBackgroundColor;
}
if (backgroundColor) {
[backgroundColor setFill];
NSRectFill(dirtyRect);
}
}
@end

View File

@ -4,7 +4,16 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.pl.bthlabs.HotPocket</string>
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)pl.bthlabs.HotPocketShared</string>
<string>$(AppIdentifierPrefix)pl.bthlabs.HotPocket.Extension</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,245 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24127" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24127"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="System colors introduced in macOS 10.14" minToolsVersion="10.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="ShareViewController">
<connections>
<outlet property="progressIndicator" destination="3hY-1S-Wo2" id="Dpk-yi-LVA"/>
<outlet property="view" destination="1" id="2"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="1">
<rect key="frame" x="0.0" y="0.0" width="388" height="258"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="339-Mg-wWu">
<rect key="frame" x="163" y="174" width="64" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="icon-mac-384" id="NT0-XU-t9f"/>
</imageView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KZW-gY-pvX">
<rect key="frame" x="18" y="138" width="352" height="28"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="HotPocket by BTHLabs" id="urI-Z1-yMm">
<font key="font" metaFont="system" size="24"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<customView id="ERc-i0-Lfz" userLabel="Error View">
<rect key="frame" x="20" y="22" width="348" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Hwr-tf-MxL">
<rect key="frame" x="140" y="-7" width="69" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Close" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="7Kd-FS-0AY">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="cancel:" target="-2" id="yRt-GR-jQ6"/>
</connections>
</button>
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5X8-4n-wWm">
<rect key="frame" x="-2" y="28" width="352" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" selectable="YES" alignment="center" title="HotPocket couldn't complete this operation." id="fmg-RT-3FA">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" id="95C-iI-GaL">
<rect key="frame" x="158" y="65" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="multiply.circle.fill" catalog="system" id="iXK-aQ-lIy"/>
<color key="contentTintColor" name="DangerColor"/>
</imageView>
</subviews>
<connections>
<binding destination="-2" name="hidden" keyPath="self.errorViewHidden" id="WUX-Pk-WLH"/>
</connections>
</customView>
<customView id="lQA-9N-11c" userLabel="Done View">
<rect key="frame" x="20" y="22" width="348" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" id="QBX-Dv-VAz">
<rect key="frame" x="158" y="65" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="checkmark.circle.fill" catalog="system" id="iFW-1s-X0l"/>
<color key="contentTintColor" name="SuccessColor"/>
</imageView>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DFX-X0-zX8">
<rect key="frame" x="140" y="-7" width="69" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Close" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="2jG-9M-YQb">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="close:" target="-2" id="3aP-Lu-EzX"/>
</connections>
</button>
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Mfx-pW-oi2">
<rect key="frame" x="-2" y="28" width="352" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" selectable="YES" alignment="center" title="Your link has been saved!" id="JhJ-K4-UFb">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<binding destination="-2" name="hidden" keyPath="self.doneViewHidden" id="4mN-62-nDm"/>
</connections>
</customView>
<customView id="XMP-x8-OMJ" userLabel="Needs Setup View">
<rect key="frame" x="20" y="22" width="348" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" id="p8k-QM-ZwX">
<rect key="frame" x="158" y="65" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyUpOrDown" image="exclamationmark.circle.fill" catalog="system" id="3kO-Gq-csg"/>
<color key="contentTintColor" name="WarningColor"/>
</imageView>
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YLC-Bx-qKZ">
<rect key="frame" x="-2" y="28" width="352" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" selectable="YES" alignment="center" title="Open the HotPocket application to set it up." id="eYb-eq-cbo">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ufJ-HY-8Ir">
<rect key="frame" x="136" y="-7" width="76" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="aH5-RV-e1O">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="cancel:" target="-2" id="ZRZ-Es-NM6"/>
</connections>
</button>
</subviews>
<connections>
<binding destination="-2" name="hidden" keyPath="self.needsSetupViewHidden" id="zQw-vW-9Y4"/>
</connections>
</customView>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Szo-4N-z5M" userLabel="Saving View">
<rect key="frame" x="20" y="22" width="348" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="b64-XQ-Anx">
<rect key="frame" x="136" y="-7" width="76" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="7U4-so-kvt">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="cancel:" target="-2" id="Aia-vP-eiA"/>
</connections>
</button>
<progressIndicator fixedFrame="YES" maxValue="100" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="3hY-1S-Wo2">
<rect key="frame" x="158" y="52" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
</progressIndicator>
</subviews>
<connections>
<binding destination="-2" name="hidden" keyPath="self.savingViewHidden" id="HNo-aa-0OR"/>
</connections>
</customView>
<customView id="cFw-BG-nDd" userLabel="Unprocessable Entity View">
<rect key="frame" x="20" y="22" width="348" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" id="hQi-Zt-thw">
<rect key="frame" x="158" y="65" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyUpOrDown" image="exclamationmark.circle.fill" catalog="system" id="66K-cT-2Vw"/>
<color key="contentTintColor" name="WarningColor"/>
</imageView>
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LS4-qN-h75">
<rect key="frame" x="-2" y="28" width="352" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" selectable="YES" alignment="center" title="This item couldn't be shared :(." id="b0i-Lf-21f">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cDA-ec-njT">
<rect key="frame" x="136" y="-7" width="76" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="lr2-gc-RFv">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="cancel:" target="-2" id="yWF-hM-ejy"/>
</connections>
</button>
</subviews>
<connections>
<binding destination="-2" name="hidden" keyPath="self.unprocessableEntityViewHidden" id="lqC-lO-ll8"/>
</connections>
</customView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1yJ-sU-Spr" userLabel="uname Label">
<rect key="frame" x="6" y="4" width="376" height="14"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="center" id="nQ0-Es-oIB">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="controlAccentColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<connections>
<binding destination="-2" name="value" keyPath="self.uname" id="rKp-CV-Mpj"/>
</connections>
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="132" y="-45"/>
</customView>
</objects>
<resources>
<image name="checkmark.circle.fill" catalog="system" width="15" height="15"/>
<image name="exclamationmark.circle.fill" catalog="system" width="15" height="15"/>
<image name="icon-mac-384" width="384" height="384"/>
<image name="multiply.circle.fill" catalog="system" width="15" height="15"/>
<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>

View File

@ -0,0 +1,31 @@
<?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>CFBundleIconFile</key>
<string>icon</string>
<key>HPAPIAccessTokenPlatform</key>
<string>macOS</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>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
<key>NSExtensionPrincipalClass</key>
<string>ShareViewController</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,24 @@
//
// ShareViewController.h
// macOS (Share Extension)
//
// Created by Tomek Wójcik on 22/09/2025.
//
#import <Cocoa/Cocoa.h>
@class HPAPI;
@interface ShareViewController : NSViewController
@property HPAPI *api;
@property BOOL savingViewHidden;
@property BOOL needsSetupViewHidden;
@property BOOL doneViewHidden;
@property BOOL errorViewHidden;
@property BOOL unprocessableEntityViewHidden;
@property NSString *uname;
@property IBOutlet NSProgressIndicator *progressIndicator;
@end

View File

@ -0,0 +1,128 @@
//
// ShareViewController.m
// macOS (Share Extension)
//
// Created by Tomek Wójcik on 22/09/2025.
//
#import "ShareViewController.h"
#import "HPAPI.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.savingViewHidden = YES;
if (result.error != nil) {
#ifdef DEBUG
NSLog(@"-[ShareViewController resolveLinkAndSave] saveError=`%@`", result.error);
#endif
self.errorViewHidden = NO;
} else {
self.doneViewHidden = NO;
}
});
}];
if (callResult == NO) {
self.savingViewHidden = YES;
self.errorViewHidden = NO;
}
}
-(void)resolveLinkAndSave {
HPShareExtensionHelper *helper = [[HPShareExtensionHelper alloc] initWithContext:self.extensionContext];
[helper processItems:^(NSURL *url) {
if (url == nil) {
self.savingViewHidden = YES;
self.unprocessableEntityViewHidden = NO;
} else {
[self saveURL:url];
}
}];
}
@end
@implementation ShareViewController
#pragma mark - View lifecycle
-(NSString *)nibName {
return @"ShareViewController";
}
-(void)viewDidLoad {
[super viewDidLoad];
self.savingViewHidden = NO;
self.needsSetupViewHidden = YES;
self.doneViewHidden = YES;
self.errorViewHidden = YES;
self.unprocessableEntityViewHidden = YES;
NSBundle *mainBundle = [NSBundle mainBundle];
self.uname = [NSString stringWithFormat:@"HotPocket v%@ (%@)", [mainBundle.infoDictionary valueForKey:@"CFBundleShortVersionString"], [mainBundle.infoDictionary valueForKey:@"CFBundleVersion"]];
self.api = [[HPAPI alloc] init];
if (self.api.rpcClient.hasCredentials == YES) {
self.savingViewHidden = NO;
self.needsSetupViewHidden = YES;
} else {
self.savingViewHidden = YES;
self.needsSetupViewHidden = NO;
}
}
-(void)viewWillAppear {
[super viewWillAppear];
[self.progressIndicator startAnimation:self];
}
-(void)viewDidAppear {
[super viewDidAppear];
[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.savingViewHidden = YES;
self.needsSetupViewHidden = NO;
} else {
[self resolveLinkAndSave];
}
});
}];
}
-(void)viewDidDisappear {
[super viewDidDisappear];
[self.progressIndicator stopAnimation:self];
}
#pragma mark - Actions
-(IBAction)close:(id)sender {
NSExtensionItem *outputItem = [[NSExtensionItem alloc] init];
NSArray *outputItems = @[outputItem];
[self.extensionContext completeRequestReturningItems:outputItems completionHandler:nil];
}
-(IBAction)cancel:(id)sender {
NSError *cancelError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil];
[self.extensionContext cancelRequestWithError:cancelError];
}
@end

Binary file not shown.

View File

@ -0,0 +1,21 @@
<?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.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.pl.bthlabs.HotPocket</string>
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)pl.bthlabs.HotPocketShared</string>
<string>$(AppIdentifierPrefix)pl.bthlabs.HotPocket.ShareExtension</string>
</array>
</dict>
</plist>

View File

511
services/apple/poetry.lock generated Normal file
View File

@ -0,0 +1,511 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "asttokens"
version = "3.0.0"
description = "Annotate AST trees with source code positions"
optional = false
python-versions = ">=3.8"
files = [
{file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"},
{file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"},
]
[package.extras]
astroid = ["astroid (>=2,<4)"]
test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"]
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "decorator"
version = "5.2.1"
description = "Decorators for Humans"
optional = false
python-versions = ">=3.8"
files = [
{file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"},
{file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"},
]
[[package]]
name = "executing"
version = "2.2.1"
description = "Get the currently executing AST node of a frame, and other information"
optional = false
python-versions = ">=3.8"
files = [
{file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"},
{file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"},
]
[package.extras]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
[[package]]
name = "factory-boy"
version = "3.3.3"
description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby."
optional = false
python-versions = ">=3.8"
files = [
{file = "factory_boy-3.3.3-py2.py3-none-any.whl", hash = "sha256:1c39e3289f7e667c4285433f305f8d506efc2fe9c73aaea4151ebd5cdea394fc"},
{file = "factory_boy-3.3.3.tar.gz", hash = "sha256:866862d226128dfac7f2b4160287e899daf54f2612778327dd03d0e2cb1e3d03"},
]
[package.dependencies]
Faker = ">=0.7.0"
[package.extras]
dev = ["Django", "Pillow", "SQLAlchemy", "coverage", "flake8", "isort", "mongoengine", "mongomock", "mypy", "tox", "wheel (>=0.32.0)", "zest.releaser[recommended]"]
doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"]
[[package]]
name = "faker"
version = "37.6.0"
description = "Faker is a Python package that generates fake data for you."
optional = false
python-versions = ">=3.9"
files = [
{file = "faker-37.6.0-py3-none-any.whl", hash = "sha256:3c5209b23d7049d596a51db5d76403a0ccfea6fc294ffa2ecfef6a8843b1e6a7"},
{file = "faker-37.6.0.tar.gz", hash = "sha256:0f8cc34f30095184adf87c3c24c45b38b33ad81c35ef6eb0a3118f301143012c"},
]
[package.dependencies]
tzdata = "*"
[[package]]
name = "flake8"
version = "7.3.0"
description = "the modular source code checker: pep8 pyflakes and co"
optional = false
python-versions = ">=3.9"
files = [
{file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"},
{file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"},
]
[package.dependencies]
mccabe = ">=0.7.0,<0.8.0"
pycodestyle = ">=2.14.0,<2.15.0"
pyflakes = ">=3.4.0,<3.5.0"
[[package]]
name = "flake8-commas"
version = "4.0.0"
description = "Flake8 lint for trailing commas."
optional = false
python-versions = ">=3.8"
files = [
{file = "flake8_commas-4.0.0-py3-none-any.whl", hash = "sha256:cad476d71ba72e8b941a8508d5b9ffb6b03e50f7102982474f085ad0d674b685"},
{file = "flake8_commas-4.0.0.tar.gz", hash = "sha256:a68834b42a9a31c94ca790efe557a932c0eae21a3479c6b9a23c4dc077e3ea96"},
]
[package.dependencies]
flake8 = ">=5"
[[package]]
name = "hotpocket-workspace-tools"
version = "1.0.0.dev0"
description = "HotPocket Workspace Tools"
optional = false
python-versions = "^3.12"
files = []
develop = true
[package.dependencies]
invoke = "2.2.0"
[package.source]
type = "directory"
url = "../packages/workspace_tools"
[[package]]
name = "invoke"
version = "2.2.0"
description = "Pythonic task execution"
optional = false
python-versions = ">=3.6"
files = [
{file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"},
{file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"},
]
[[package]]
name = "ipdb"
version = "0.13.13"
description = "IPython-enabled pdb"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
{file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"},
{file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"},
]
[package.dependencies]
decorator = {version = "*", markers = "python_version >= \"3.11\""}
ipython = {version = ">=7.31.1", markers = "python_version >= \"3.11\""}
[[package]]
name = "ipython"
version = "9.3.0"
description = "IPython: Productive Interactive Computing"
optional = false
python-versions = ">=3.11"
files = [
{file = "ipython-9.3.0-py3-none-any.whl", hash = "sha256:1a0b6dd9221a1f5dddf725b57ac0cb6fddc7b5f470576231ae9162b9b3455a04"},
{file = "ipython-9.3.0.tar.gz", hash = "sha256:79eb896f9f23f50ad16c3bc205f686f6e030ad246cc309c6279a242b14afe9d8"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
decorator = "*"
ipython-pygments-lexers = "*"
jedi = ">=0.16"
matplotlib-inline = "*"
pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""}
prompt_toolkit = ">=3.0.41,<3.1.0"
pygments = ">=2.4.0"
stack_data = "*"
traitlets = ">=5.13.0"
[package.extras]
all = ["ipython[doc,matplotlib,test,test-extra]"]
black = ["black"]
doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinx_toml (==0.0.4)", "typing_extensions"]
matplotlib = ["matplotlib"]
test = ["packaging", "pytest", "pytest-asyncio (<0.22)", "testpath"]
test-extra = ["curio", "ipykernel", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "nbclient", "nbformat", "numpy (>=1.23)", "pandas", "trio"]
[[package]]
name = "ipython-pygments-lexers"
version = "1.1.1"
description = "Defines a variety of Pygments lexers for highlighting IPython code."
optional = false
python-versions = ">=3.8"
files = [
{file = "ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c"},
{file = "ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81"},
]
[package.dependencies]
pygments = "*"
[[package]]
name = "isort"
version = "6.0.1"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.9.0"
files = [
{file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"},
{file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"},
]
[package.extras]
colors = ["colorama"]
plugins = ["setuptools"]
[[package]]
name = "jedi"
version = "0.19.2"
description = "An autocompletion tool for Python that can be used for text editors."
optional = false
python-versions = ">=3.6"
files = [
{file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"},
{file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"},
]
[package.dependencies]
parso = ">=0.8.4,<0.9.0"
[package.extras]
docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"]
[[package]]
name = "matplotlib-inline"
version = "0.1.7"
description = "Inline Matplotlib backend for Jupyter"
optional = false
python-versions = ">=3.8"
files = [
{file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"},
{file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"},
]
[package.dependencies]
traitlets = "*"
[[package]]
name = "mccabe"
version = "0.7.0"
description = "McCabe checker, plugin for flake8"
optional = false
python-versions = ">=3.6"
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
[[package]]
name = "mypy"
version = "1.16.1"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.9"
files = [
{file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"},
{file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"},
{file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"},
{file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"},
{file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"},
{file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"},
{file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"},
{file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"},
{file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"},
{file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"},
{file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"},
{file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"},
{file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"},
{file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"},
{file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"},
{file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"},
{file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"},
{file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"},
{file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"},
{file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"},
{file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"},
{file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"},
{file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"},
{file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"},
{file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"},
{file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"},
{file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"},
{file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"},
{file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"},
{file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"},
{file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"},
{file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"},
]
[package.dependencies]
mypy_extensions = ">=1.0.0"
pathspec = ">=0.9.0"
typing_extensions = ">=4.6.0"
[package.extras]
dmypy = ["psutil (>=4.0)"]
faster-cache = ["orjson"]
install-types = ["pip"]
mypyc = ["setuptools (>=50)"]
reports = ["lxml"]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.8"
files = [
{file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
{file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
]
[[package]]
name = "parso"
version = "0.8.5"
description = "A Python Parser"
optional = false
python-versions = ">=3.6"
files = [
{file = "parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887"},
{file = "parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a"},
]
[package.extras]
qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
testing = ["docopt", "pytest"]
[[package]]
name = "pathspec"
version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
name = "pexpect"
version = "4.9.0"
description = "Pexpect allows easy control of interactive console applications."
optional = false
python-versions = "*"
files = [
{file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
{file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
]
[package.dependencies]
ptyprocess = ">=0.5"
[[package]]
name = "prompt-toolkit"
version = "3.0.52"
description = "Library for building powerful interactive command lines in Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"},
{file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"},
]
[package.dependencies]
wcwidth = "*"
[[package]]
name = "ptyprocess"
version = "0.7.0"
description = "Run a subprocess in a pseudo terminal"
optional = false
python-versions = "*"
files = [
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
]
[[package]]
name = "pure-eval"
version = "0.2.3"
description = "Safely evaluate AST nodes without side effects"
optional = false
python-versions = "*"
files = [
{file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"},
{file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"},
]
[package.extras]
tests = ["pytest"]
[[package]]
name = "pycodestyle"
version = "2.14.0"
description = "Python style guide checker"
optional = false
python-versions = ">=3.9"
files = [
{file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"},
{file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"},
]
[[package]]
name = "pyflakes"
version = "3.4.0"
description = "passive checker of Python programs"
optional = false
python-versions = ">=3.9"
files = [
{file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"},
{file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"},
]
[[package]]
name = "pygments"
version = "2.19.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
files = [
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "stack-data"
version = "0.6.3"
description = "Extract data from python stack frames and tracebacks for informative displays"
optional = false
python-versions = "*"
files = [
{file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
{file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
]
[package.dependencies]
asttokens = ">=2.1.0"
executing = ">=1.2.0"
pure-eval = "*"
[package.extras]
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
[[package]]
name = "traitlets"
version = "5.14.3"
description = "Traitlets Python configuration system"
optional = false
python-versions = ">=3.8"
files = [
{file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"},
{file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"},
]
[package.extras]
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
[[package]]
name = "typing-extensions"
version = "4.15.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
files = [
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
]
[[package]]
name = "tzdata"
version = "2025.2"
description = "Provider of IANA time zone data"
optional = false
python-versions = ">=2"
files = [
{file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"},
{file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"},
]
[[package]]
name = "wcwidth"
version = "0.2.13"
description = "Measures the displayed width of unicode strings in a terminal"
optional = false
python-versions = "*"
files = [
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "1c74b6d5928a0b1bde41962f4233af2eba2242324c3155b21093b2f46baf5cd0"

Some files were not shown because too many files have changed in this diff Show More