├── .github └── workflows │ ├── alibabacloud.yml │ ├── android.yml │ ├── aws.yml │ ├── azure-functions-app-dotnet.yml │ ├── azure-functions-app-nodejs.yml │ ├── azure-functions-app-powershell.yml │ ├── azure-kubernetes-service.yml │ ├── azure-webapps-dotnet-core.yml │ ├── azure-webapps-node.yml │ ├── azure-webapps-python.yml │ ├── codeql.yml │ ├── dependency-review.yml │ ├── dotnet-desktop.yml │ ├── dotnet.yml │ ├── google-cloudrun-source.yml │ ├── greetings.yml │ ├── ios.yml │ ├── jekyll-docker.yml │ ├── label.yml │ ├── manual.yml │ ├── node.js.yml │ ├── npm-publish-github-packages.yml │ ├── objective-c-xcode.yml │ ├── python-package-conda.yml │ ├── python-publish.yml │ ├── stale.yml │ ├── tencent.yml │ └── webpack.yml ├── .gitignore ├── .whitesource ├── LICENSE.md ├── PiNetwork.Blazor.Sdk ├── Dto │ ├── Auth │ │ ├── AuthMe.cs │ │ ├── AuthMe.json │ │ ├── AuthResultDto.cs │ │ └── AuthResultDto.json │ ├── Payment │ │ ├── PaymentCompleteDto.cs │ │ ├── PaymentDto.cs │ │ └── PaymentDto.json │ └── TransactionA2UView.cs ├── Javascript │ └── PiNetworkCallJavascript.cs ├── Pages │ ├── PiNetworkMain.razor │ └── PiNetworkMain.razor.cs ├── PiNetwork.Blazor.Sdk.csproj ├── PiNetworkA2UServerBlazor.cs ├── PiNetworkClientBlazor.cs ├── PiNetworkCommon.cs ├── PiNetworkU2AServerBlazor.cs ├── ServiceCollectionExtensions.cs ├── bundleconfig.json └── wwwroot │ ├── PiNetwork.Blazor.Sdk.js │ └── PiNetwork.Blazor.Sdk.min.js ├── Readme.md ├── ServicesPiNetworkU2AFacade.md └── src └── constants.py /.github/workflows/alibabacloud.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and push a new container image to Alibaba Cloud Container Registry (ACR), 2 | # and then will deploy it to Alibaba Cloud Container Service for Kubernetes (ACK), when there is a push to the "main" branch. 3 | # 4 | # To use this workflow, you will need to complete the following set-up steps: 5 | # 6 | # 1. Create an ACR repository to store your container images. 7 | # You can use ACR EE instance for more security and better performance. 8 | # For instructions see https://www.alibabacloud.com/help/doc-detail/142168.htm 9 | # 10 | # 2. Create an ACK cluster to run your containerized application. 11 | # You can use ACK Pro cluster for more security and better performance. 12 | # For instructions see https://www.alibabacloud.com/help/doc-detail/95108.htm 13 | # 14 | # 3. Store your AccessKey pair in GitHub Actions secrets named `ACCESS_KEY_ID` and `ACCESS_KEY_SECRET`. 15 | # For instructions on setting up secrets see: https://developer.github.com/actions/managing-workflows/storing-secrets/ 16 | # 17 | # 4. Change the values for the REGION_ID, REGISTRY, NAMESPACE, IMAGE, ACK_CLUSTER_ID, and ACK_DEPLOYMENT_NAME. 18 | # 19 | 20 | name: Build and Deploy to ACK 21 | 22 | on: 23 | push: 24 | branches: [ "main" ] 25 | 26 | # Environment variables available to all jobs and steps in this workflow. 27 | env: 28 | REGION_ID: cn-hangzhou 29 | REGISTRY: registry.cn-hangzhou.aliyuncs.com 30 | NAMESPACE: namespace 31 | IMAGE: repo 32 | TAG: ${{ github.sha }} 33 | ACK_CLUSTER_ID: clusterID 34 | ACK_DEPLOYMENT_NAME: nginx-deployment 35 | 36 | ACR_EE_REGISTRY: myregistry.cn-hangzhou.cr.aliyuncs.com 37 | ACR_EE_INSTANCE_ID: instanceID 38 | ACR_EE_NAMESPACE: namespace 39 | ACR_EE_IMAGE: repo 40 | ACR_EE_TAG: ${{ github.sha }} 41 | 42 | permissions: 43 | contents: read 44 | 45 | jobs: 46 | build: 47 | runs-on: ubuntu-latest 48 | environment: production 49 | 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v3 53 | 54 | # 1.1 Login to ACR 55 | - name: Login to ACR with the AccessKey pair 56 | uses: aliyun/acr-login@v1 57 | with: 58 | region-id: "${{ env.REGION_ID }}" 59 | access-key-id: "${{ secrets.ACCESS_KEY_ID }}" 60 | access-key-secret: "${{ secrets.ACCESS_KEY_SECRET }}" 61 | 62 | # 1.2 Buid and push image to ACR 63 | - name: Build and push image to ACR 64 | run: | 65 | docker build --tag "$REGISTRY/$NAMESPACE/$IMAGE:$TAG" . 66 | docker push "$REGISTRY/$NAMESPACE/$IMAGE:$TAG" 67 | 68 | # 1.3 Scan image in ACR 69 | - name: Scan image in ACR 70 | uses: aliyun/acr-scan@v1 71 | with: 72 | region-id: "${{ env.REGION_ID }}" 73 | access-key-id: "${{ secrets.ACCESS_KEY_ID }}" 74 | access-key-secret: "${{ secrets.ACCESS_KEY_SECRET }}" 75 | repository: "${{ env.NAMESPACE }}/${{ env.IMAGE }}" 76 | tag: "${{ env.TAG }}" 77 | 78 | # 2.1 (Optional) Login to ACR EE 79 | - uses: actions/checkout@v3 80 | - name: Login to ACR EE with the AccessKey pair 81 | uses: aliyun/acr-login@v1 82 | with: 83 | login-server: "https://${{ env.ACR_EE_REGISTRY }}" 84 | region-id: "${{ env.REGION_ID }}" 85 | access-key-id: "${{ secrets.ACCESS_KEY_ID }}" 86 | access-key-secret: "${{ secrets.ACCESS_KEY_SECRET }}" 87 | instance-id: "${{ env.ACR_EE_INSTANCE_ID }}" 88 | 89 | # 2.2 (Optional) Build and push image ACR EE 90 | - name: Build and push image to ACR EE 91 | run: | 92 | docker build -t "$ACR_EE_REGISTRY/$ACR_EE_NAMESPACE/$ACR_EE_IMAGE:$TAG" . 93 | docker push "$ACR_EE_REGISTRY/$ACR_EE_NAMESPACE/$ACR_EE_IMAGE:$TAG" 94 | # 2.3 (Optional) Scan image in ACR EE 95 | - name: Scan image in ACR EE 96 | uses: aliyun/acr-scan@v1 97 | with: 98 | region-id: "${{ env.REGION_ID }}" 99 | access-key-id: "${{ secrets.ACCESS_KEY_ID }}" 100 | access-key-secret: "${{ secrets.ACCESS_KEY_SECRET }}" 101 | instance-id: "${{ env.ACR_EE_INSTANCE_ID }}" 102 | repository: "${{ env.ACR_EE_NAMESPACE}}/${{ env.ACR_EE_IMAGE }}" 103 | tag: "${{ env.ACR_EE_TAG }}" 104 | 105 | # 3.1 Set ACK context 106 | - name: Set K8s context 107 | uses: aliyun/ack-set-context@v1 108 | with: 109 | access-key-id: "${{ secrets.ACCESS_KEY_ID }}" 110 | access-key-secret: "${{ secrets.ACCESS_KEY_SECRET }}" 111 | cluster-id: "${{ env.ACK_CLUSTER_ID }}" 112 | 113 | # 3.2 Deploy the image to the ACK cluster 114 | - name: Set up Kustomize 115 | run: |- 116 | curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash /dev/stdin 3.8.6 117 | - name: Deploy 118 | run: |- 119 | ./kustomize edit set image REGISTRY/NAMESPACE/IMAGE:TAG=$REGISTRY/$NAMESPACE/$IMAGE:$TAG 120 | ./kustomize build . | kubectl apply -f - 121 | kubectl rollout status deployment/$ACK_DEPLOYMENT_NAME 122 | kubectl get services -o wide 123 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: set up JDK 11 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: '11' 20 | distribution: 'temurin' 21 | cache: gradle 22 | 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Build with Gradle 26 | run: ./gradlew build 27 | -------------------------------------------------------------------------------- /.github/workflows/aws.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and push a new container image to Amazon ECR, 2 | # and then will deploy a new task definition to Amazon ECS, when there is a push to the "main" branch. 3 | # 4 | # To use this workflow, you will need to complete the following set-up steps: 5 | # 6 | # 1. Create an ECR repository to store your images. 7 | # For example: `aws ecr create-repository --repository-name my-ecr-repo --region us-east-2`. 8 | # Replace the value of the `ECR_REPOSITORY` environment variable in the workflow below with your repository's name. 9 | # Replace the value of the `AWS_REGION` environment variable in the workflow below with your repository's region. 10 | # 11 | # 2. Create an ECS task definition, an ECS cluster, and an ECS service. 12 | # For example, follow the Getting Started guide on the ECS console: 13 | # https://us-east-2.console.aws.amazon.com/ecs/home?region=us-east-2#/firstRun 14 | # Replace the value of the `ECS_SERVICE` environment variable in the workflow below with the name you set for the Amazon ECS service. 15 | # Replace the value of the `ECS_CLUSTER` environment variable in the workflow below with the name you set for the cluster. 16 | # 17 | # 3. Store your ECS task definition as a JSON file in your repository. 18 | # The format should follow the output of `aws ecs register-task-definition --generate-cli-skeleton`. 19 | # Replace the value of the `ECS_TASK_DEFINITION` environment variable in the workflow below with the path to the JSON file. 20 | # Replace the value of the `CONTAINER_NAME` environment variable in the workflow below with the name of the container 21 | # in the `containerDefinitions` section of the task definition. 22 | # 23 | # 4. Store an IAM user access key in GitHub Actions secrets named `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. 24 | # See the documentation for each action used below for the recommended IAM policies for this IAM user, 25 | # and best practices on handling the access key credentials. 26 | 27 | name: Deploy to Amazon ECS 28 | 29 | on: 30 | push: 31 | branches: [ "main" ] 32 | 33 | env: 34 | AWS_REGION: MY_AWS_REGION # set this to your preferred AWS region, e.g. us-west-1 35 | ECR_REPOSITORY: MY_ECR_REPOSITORY # set this to your Amazon ECR repository name 36 | ECS_SERVICE: MY_ECS_SERVICE # set this to your Amazon ECS service name 37 | ECS_CLUSTER: MY_ECS_CLUSTER # set this to your Amazon ECS cluster name 38 | ECS_TASK_DEFINITION: MY_ECS_TASK_DEFINITION # set this to the path to your Amazon ECS task definition 39 | # file, e.g. .aws/task-definition.json 40 | CONTAINER_NAME: MY_CONTAINER_NAME # set this to the name of the container in the 41 | # containerDefinitions section of your task definition 42 | 43 | permissions: 44 | contents: read 45 | 46 | jobs: 47 | deploy: 48 | name: Deploy 49 | runs-on: ubuntu-latest 50 | environment: production 51 | 52 | steps: 53 | - name: Checkout 54 | uses: actions/checkout@v3 55 | 56 | - name: Configure AWS credentials 57 | uses: aws-actions/configure-aws-credentials@v1 58 | with: 59 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 60 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 61 | aws-region: ${{ env.AWS_REGION }} 62 | 63 | - name: Login to Amazon ECR 64 | id: login-ecr 65 | uses: aws-actions/amazon-ecr-login@v1 66 | 67 | - name: Build, tag, and push image to Amazon ECR 68 | id: build-image 69 | env: 70 | ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} 71 | IMAGE_TAG: ${{ github.sha }} 72 | run: | 73 | # Build a docker container and 74 | # push it to ECR so that it can 75 | # be deployed to ECS. 76 | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . 77 | docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG 78 | echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT 79 | 80 | - name: Fill in the new image ID in the Amazon ECS task definition 81 | id: task-def 82 | uses: aws-actions/amazon-ecs-render-task-definition@v1 83 | with: 84 | task-definition: ${{ env.ECS_TASK_DEFINITION }} 85 | container-name: ${{ env.CONTAINER_NAME }} 86 | image: ${{ steps.build-image.outputs.image }} 87 | 88 | - name: Deploy Amazon ECS task definition 89 | uses: aws-actions/amazon-ecs-deploy-task-definition@v1 90 | with: 91 | task-definition: ${{ steps.task-def.outputs.task-definition }} 92 | service: ${{ env.ECS_SERVICE }} 93 | cluster: ${{ env.ECS_CLUSTER }} 94 | wait-for-service-stability: true 95 | -------------------------------------------------------------------------------- /.github/workflows/azure-functions-app-dotnet.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET Core project and deploy it to an Azure Functions App on Windows or Linux when a commit is pushed to your default branch. 2 | # 3 | # This workflow assumes you have already created the target Azure Functions app. 4 | # For instructions see https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-csharp?tabs=in-process 5 | # 6 | # To configure this workflow: 7 | # 1. Set up the following secrets in your repository: 8 | # - AZURE_FUNCTIONAPP_PUBLISH_PROFILE 9 | # 2. Change env variables for your configuration. 10 | # 11 | # For more information on: 12 | # - GitHub Actions for Azure: https://github.com/Azure/Actions 13 | # - Azure Functions Action: https://github.com/Azure/functions-action 14 | # - Publish Profile: https://github.com/Azure/functions-action#using-publish-profile-as-deployment-credential-recommended 15 | # - Azure Service Principal for RBAC: https://github.com/Azure/functions-action#using-azure-service-principal-for-rbac-as-deployment-credential 16 | # 17 | # For more samples to get started with GitHub Action workflows to deploy to Azure: https://github.com/Azure/actions-workflow-samples/tree/master/FunctionApp 18 | 19 | name: Deploy DotNet project to Azure Function App 20 | 21 | on: 22 | push: 23 | branches: 24 | - ["main"] 25 | 26 | env: 27 | AZURE_FUNCTIONAPP_NAME: 'your-app-name' # set this to your function app name on Azure 28 | AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your function app project, defaults to the repository root 29 | DOTNET_VERSION: '6.0.x' # set this to the dotnet version to use (e.g. '2.1.x', '3.1.x', '5.0.x') 30 | 31 | jobs: 32 | build-and-deploy: 33 | runs-on: windows-latest # For Linux, use ubuntu-latest 34 | environment: dev 35 | steps: 36 | - name: 'Checkout GitHub Action' 37 | uses: actions/checkout@v3 38 | 39 | # If you want to use Azure RBAC instead of Publish Profile, then uncomment the task below 40 | # - name: 'Login via Azure CLI' 41 | # uses: azure/login@v1 42 | # with: 43 | # creds: ${{ secrets.AZURE_RBAC_CREDENTIALS }} # set up AZURE_RBAC_CREDENTIALS secrets in your repository 44 | 45 | - name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment 46 | uses: actions/setup-dotnet@v3 47 | with: 48 | dotnet-version: ${{ env.DOTNET_VERSION }} 49 | 50 | - name: 'Resolve Project Dependencies Using Dotnet' 51 | shell: pwsh # For Linux, use bash 52 | run: | 53 | pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}' 54 | dotnet build --configuration Release --output ./output 55 | popd 56 | 57 | - name: 'Run Azure Functions Action' 58 | uses: Azure/functions-action@v1 59 | id: fa 60 | with: 61 | app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }} 62 | package: '${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output' 63 | publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }} # Remove publish-profile to use Azure RBAC 64 | -------------------------------------------------------------------------------- /.github/workflows/azure-functions-app-nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Node.js project and deploy it to an Azure Functions App on Windows or Linux when a commit is pushed to your default branch. 2 | # 3 | # This workflow assumes you have already created the target Azure Functions app. 4 | # For instructions see: 5 | # - https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-node 6 | # - https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-typescript 7 | # 8 | # To configure this workflow: 9 | # 1. Set up the following secrets in your repository: 10 | # - AZURE_FUNCTIONAPP_PUBLISH_PROFILE 11 | # 2. Change env variables for your configuration. 12 | # 13 | # For more information on: 14 | # - GitHub Actions for Azure: https://github.com/Azure/Actions 15 | # - Azure Functions Action: https://github.com/Azure/functions-action 16 | # - Publish Profile: https://github.com/Azure/functions-action#using-publish-profile-as-deployment-credential-recommended 17 | # - Azure Service Principal for RBAC: https://github.com/Azure/functions-action#using-azure-service-principal-for-rbac-as-deployment-credential 18 | # 19 | # For more samples to get started with GitHub Action workflows to deploy to Azure: https://github.com/Azure/actions-workflow-samples/tree/master/FunctionApp 20 | 21 | name: Deploy Node.js project to Azure Function App 22 | 23 | on: 24 | push: 25 | branches: 26 | - ["main"] 27 | 28 | env: 29 | AZURE_FUNCTIONAPP_NAME: 'your-app-name' # set this to your function app name on Azure 30 | AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your function app project, defaults to the repository root 31 | NODE_VERSION: '16.x' # set this to the node version to use (e.g. '8.x', '10.x', '12.x') 32 | 33 | jobs: 34 | build-and-deploy: 35 | runs-on: windows-latest # For Linux, use ubuntu-latest 36 | environment: dev 37 | steps: 38 | - name: 'Checkout GitHub Action' 39 | uses: actions/checkout@v3 40 | 41 | # If you want to use Azure RBAC instead of Publish Profile, then uncomment the task below 42 | # - name: 'Login via Azure CLI' 43 | # uses: azure/login@v1 44 | # with: 45 | # creds: ${{ secrets.AZURE_RBAC_CREDENTIALS }} # set up AZURE_RBAC_CREDENTIALS secrets in your repository 46 | 47 | - name: Setup Node ${{ env.NODE_VERSION }} Environment 48 | uses: actions/setup-node@v3 49 | with: 50 | node-version: ${{ env.NODE_VERSION }} 51 | 52 | - name: 'Resolve Project Dependencies Using Npm' 53 | shell: pwsh # For Linux, use bash 54 | run: | 55 | pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}' 56 | npm install 57 | npm run build --if-present 58 | npm run test --if-present 59 | popd 60 | 61 | - name: 'Run Azure Functions Action' 62 | uses: Azure/functions-action@v1 63 | id: fa 64 | with: 65 | app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }} 66 | package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} 67 | publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }} # Remove publish-profile to use Azure RBAC 68 | -------------------------------------------------------------------------------- /.github/workflows/azure-functions-app-powershell.yml: -------------------------------------------------------------------------------- 1 | # This workflow will deploy a PowerShell project to an Azure Functions App on Windows or Linux when a commit is pushed to your default branch. 2 | # 3 | # This workflow assumes you have already created the target Azure Functions app. 4 | # For instructions see https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-powershell 5 | # 6 | # To configure this workflow: 7 | # 1. Set up the following secrets in your repository: 8 | # - AZURE_FUNCTIONAPP_PUBLISH_PROFILE 9 | # 2. Change env variables for your configuration. 10 | # 11 | # For more information on: 12 | # - GitHub Actions for Azure: https://github.com/Azure/Actions 13 | # - Azure Functions Action: https://github.com/Azure/functions-action 14 | # - Publish Profile: https://github.com/Azure/functions-action#using-publish-profile-as-deployment-credential-recommended 15 | # - Azure Service Principal for RBAC: https://github.com/Azure/functions-action#using-azure-service-principal-for-rbac-as-deployment-credential 16 | # 17 | # For more samples to get started with GitHub Action workflows to deploy to Azure: https://github.com/Azure/actions-workflow-samples/tree/master/FunctionApp 18 | 19 | name: Deploy PowerShell project to Azure Function App 20 | 21 | on: 22 | push: 23 | branches: 24 | - ["main"] 25 | 26 | env: 27 | AZURE_FUNCTIONAPP_NAME: 'your-app-name' # set this to your function app name on Azure 28 | AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your function app project, defaults to the repository root 29 | 30 | jobs: 31 | build-and-deploy: 32 | runs-on: windows-latest # For Linux, use ubuntu-latest 33 | environment: dev 34 | steps: 35 | - name: 'Checkout GitHub Action' 36 | uses: actions/checkout@v3 37 | 38 | # If you want to use Azure RBAC instead of Publish Profile, then uncomment the task below 39 | # - name: 'Login via Azure CLI' 40 | # uses: azure/login@v1 41 | # with: 42 | # creds: ${{ secrets.AZURE_RBAC_CREDENTIALS }} # set up AZURE_RBAC_CREDENTIALS secrets in your repository 43 | 44 | - name: 'Run Azure Functions Action' 45 | uses: Azure/functions-action@v1 46 | id: fa 47 | with: 48 | app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }} 49 | package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} 50 | publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }} # Remove publish-profile to use Azure RBAC 51 | -------------------------------------------------------------------------------- /.github/workflows/azure-kubernetes-service.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and push an application to a Azure Kubernetes Service (AKS) cluster when you push your code 2 | # 3 | # This workflow assumes you have already created the target AKS cluster and have created an Azure Container Registry (ACR) 4 | # The ACR should be attached to the AKS cluster 5 | # For instructions see: 6 | # - https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough-portal 7 | # - https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal 8 | # - https://learn.microsoft.com/en-us/azure/aks/cluster-container-registry-integration?tabs=azure-cli#configure-acr-integration-for-existing-aks-clusters 9 | # - https://github.com/Azure/aks-create-action 10 | # 11 | # To configure this workflow: 12 | # 13 | # 1. Set the following secrets in your repository (instructions for getting these can be found at https://docs.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-cli%2Clinux): 14 | # - AZURE_CLIENT_ID 15 | # - AZURE_TENANT_ID 16 | # - AZURE_SUBSCRIPTION_ID 17 | # 18 | # 2. Set the following environment variables (or replace the values below): 19 | # - AZURE_CONTAINER_REGISTRY (name of your container registry / ACR) 20 | # - RESOURCE_GROUP (where your cluster is deployed) 21 | # - CLUSTER_NAME (name of your AKS cluster) 22 | # - CONTAINER_NAME (name of the container image you would like to push up to your ACR) 23 | # - IMAGE_PULL_SECRET_NAME (name of the ImagePullSecret that will be created to pull your ACR image) 24 | # - DEPLOYMENT_MANIFEST_PATH (path to the manifest yaml for your deployment) 25 | # 26 | # For more information on GitHub Actions for Azure, refer to https://github.com/Azure/Actions 27 | # For more samples to get started with GitHub Action workflows to deploy to Azure, refer to https://github.com/Azure/actions-workflow-samples 28 | # For more options with the actions used below please refer to https://github.com/Azure/login 29 | 30 | name: Build and deploy an app to AKS 31 | 32 | on: 33 | push: 34 | branches: ["main"] 35 | workflow_dispatch: 36 | 37 | env: 38 | AZURE_CONTAINER_REGISTRY: "your-azure-container-registry" 39 | CONTAINER_NAME: "your-container-name" 40 | RESOURCE_GROUP: "your-resource-group" 41 | CLUSTER_NAME: "your-cluster-name" 42 | DEPLOYMENT_MANIFEST_PATH: "your-deployment-manifest-path" 43 | 44 | jobs: 45 | buildImage: 46 | permissions: 47 | contents: read 48 | id-token: write 49 | runs-on: ubuntu-latest 50 | steps: 51 | # Checks out the repository this file is in 52 | - uses: actions/checkout@v3 53 | 54 | # Logs in with your Azure credentials 55 | - name: Azure login 56 | uses: azure/login@v1.4.6 57 | with: 58 | client-id: ${{ secrets.AZURE_CLIENT_ID }} 59 | tenant-id: ${{ secrets.AZURE_TENANT_ID }} 60 | subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} 61 | 62 | # Builds and pushes an image up to your Azure Container Registry 63 | - name: Build and push image to ACR 64 | run: | 65 | az acr build --image ${{ env.AZURE_CONTAINER_REGISTRY }}.azurecr.io/${{ env.CONTAINER_NAME }}:${{ github.sha }} --registry ${{ env.AZURE_CONTAINER_REGISTRY }} -g ${{ env.RESOURCE_GROUP }} . 66 | 67 | deploy: 68 | permissions: 69 | actions: read 70 | contents: read 71 | id-token: write 72 | runs-on: ubuntu-latest 73 | needs: [buildImage] 74 | steps: 75 | # Checks out the repository this file is in 76 | - uses: actions/checkout@v3 77 | 78 | # Logs in with your Azure credentials 79 | - name: Azure login 80 | uses: azure/login@v1.4.6 81 | with: 82 | client-id: ${{ secrets.AZURE_CLIENT_ID }} 83 | tenant-id: ${{ secrets.AZURE_TENANT_ID }} 84 | subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} 85 | 86 | # Use kubelogin to configure your kubeconfig for Azure auth 87 | - name: Set up kubelogin for non-interactive login 88 | uses: azure/use-kubelogin@v1 89 | with: 90 | kubelogin-version: 'v0.0.25' 91 | 92 | # Retrieves your Azure Kubernetes Service cluster's kubeconfig file 93 | - name: Get K8s context 94 | uses: azure/aks-set-context@v3 95 | with: 96 | resource-group: ${{ env.RESOURCE_GROUP }} 97 | cluster-name: ${{ env.CLUSTER_NAME }} 98 | admin: 'false' 99 | use-kubelogin: 'true' 100 | 101 | # Deploys application based on given manifest file 102 | - name: Deploys application 103 | uses: Azure/k8s-deploy@v4 104 | with: 105 | action: deploy 106 | manifests: ${{ env.DEPLOYMENT_MANIFEST_PATH }} 107 | images: | 108 | ${{ env.AZURE_CONTAINER_REGISTRY }}.azurecr.io/${{ env.CONTAINER_NAME }}:${{ github.sha }} 109 | -------------------------------------------------------------------------------- /.github/workflows/azure-webapps-dotnet-core.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and push a .NET Core app to an Azure Web App when a commit is pushed to your default branch. 2 | # 3 | # This workflow assumes you have already created the target Azure App Service web app. 4 | # For instructions see https://docs.microsoft.com/en-us/azure/app-service/quickstart-dotnetcore?tabs=net60&pivots=development-environment-vscode 5 | # 6 | # To configure this workflow: 7 | # 8 | # 1. Download the Publish Profile for your Azure Web App. You can download this file from the Overview page of your Web App in the Azure Portal. 9 | # For more information: https://docs.microsoft.com/en-us/azure/app-service/deploy-github-actions?tabs=applevel#generate-deployment-credentials 10 | # 11 | # 2. Create a secret in your repository named AZURE_WEBAPP_PUBLISH_PROFILE, paste the publish profile contents as the value of the secret. 12 | # For instructions on obtaining the publish profile see: https://docs.microsoft.com/azure/app-service/deploy-github-actions#configure-the-github-secret 13 | # 14 | # 3. Change the value for the AZURE_WEBAPP_NAME. Optionally, change the AZURE_WEBAPP_PACKAGE_PATH and DOTNET_VERSION environment variables below. 15 | # 16 | # For more information on GitHub Actions for Azure: https://github.com/Azure/Actions 17 | # For more information on the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy 18 | # For more samples to get started with GitHub Action workflows to deploy to Azure: https://github.com/Azure/actions-workflow-samples 19 | 20 | name: Build and deploy ASP.Net Core app to an Azure Web App 21 | 22 | env: 23 | AZURE_WEBAPP_NAME: your-app-name # set this to the name of your Azure Web App 24 | AZURE_WEBAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root 25 | DOTNET_VERSION: '5' # set this to the .NET Core version to use 26 | 27 | on: 28 | push: 29 | branches: [ "main" ] 30 | workflow_dispatch: 31 | 32 | permissions: 33 | contents: read 34 | 35 | jobs: 36 | build: 37 | runs-on: ubuntu-latest 38 | 39 | steps: 40 | - uses: actions/checkout@v3 41 | 42 | - name: Set up .NET Core 43 | uses: actions/setup-dotnet@v2 44 | with: 45 | dotnet-version: ${{ env.DOTNET_VERSION }} 46 | 47 | - name: Set up dependency caching for faster builds 48 | uses: actions/cache@v3 49 | with: 50 | path: ~/.nuget/packages 51 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} 52 | restore-keys: | 53 | ${{ runner.os }}-nuget- 54 | 55 | - name: Build with dotnet 56 | run: dotnet build --configuration Release 57 | 58 | - name: dotnet publish 59 | run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/myapp 60 | 61 | - name: Upload artifact for deployment job 62 | uses: actions/upload-artifact@v3 63 | with: 64 | name: .net-app 65 | path: ${{env.DOTNET_ROOT}}/myapp 66 | 67 | deploy: 68 | permissions: 69 | contents: none 70 | runs-on: ubuntu-latest 71 | needs: build 72 | environment: 73 | name: 'Development' 74 | url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} 75 | 76 | steps: 77 | - name: Download artifact from build job 78 | uses: actions/download-artifact@v3 79 | with: 80 | name: .net-app 81 | 82 | - name: Deploy to Azure Web App 83 | id: deploy-to-webapp 84 | uses: azure/webapps-deploy@v2 85 | with: 86 | app-name: ${{ env.AZURE_WEBAPP_NAME }} 87 | publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} 88 | package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} 89 | -------------------------------------------------------------------------------- /.github/workflows/azure-webapps-node.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and push a node.js application to an Azure Web App when a commit is pushed to your default branch. 2 | # 3 | # This workflow assumes you have already created the target Azure App Service web app. 4 | # For instructions see https://docs.microsoft.com/en-us/azure/app-service/quickstart-nodejs?tabs=linux&pivots=development-environment-cli 5 | # 6 | # To configure this workflow: 7 | # 8 | # 1. Download the Publish Profile for your Azure Web App. You can download this file from the Overview page of your Web App in the Azure Portal. 9 | # For more information: https://docs.microsoft.com/en-us/azure/app-service/deploy-github-actions?tabs=applevel#generate-deployment-credentials 10 | # 11 | # 2. Create a secret in your repository named AZURE_WEBAPP_PUBLISH_PROFILE, paste the publish profile contents as the value of the secret. 12 | # For instructions on obtaining the publish profile see: https://docs.microsoft.com/azure/app-service/deploy-github-actions#configure-the-github-secret 13 | # 14 | # 3. Change the value for the AZURE_WEBAPP_NAME. Optionally, change the AZURE_WEBAPP_PACKAGE_PATH and NODE_VERSION environment variables below. 15 | # 16 | # For more information on GitHub Actions for Azure: https://github.com/Azure/Actions 17 | # For more information on the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy 18 | # For more samples to get started with GitHub Action workflows to deploy to Azure: https://github.com/Azure/actions-workflow-samples 19 | 20 | on: 21 | push: 22 | branches: [ "main" ] 23 | workflow_dispatch: 24 | 25 | env: 26 | AZURE_WEBAPP_NAME: your-app-name # set this to your application's name 27 | AZURE_WEBAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root 28 | NODE_VERSION: '14.x' # set this to the node version to use 29 | 30 | permissions: 31 | contents: read 32 | 33 | jobs: 34 | build: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v3 38 | 39 | - name: Set up Node.js 40 | uses: actions/setup-node@v3 41 | with: 42 | node-version: ${{ env.NODE_VERSION }} 43 | cache: 'npm' 44 | 45 | - name: npm install, build, and test 46 | run: | 47 | npm install 48 | npm run build --if-present 49 | npm run test --if-present 50 | 51 | - name: Upload artifact for deployment job 52 | uses: actions/upload-artifact@v3 53 | with: 54 | name: node-app 55 | path: . 56 | 57 | deploy: 58 | permissions: 59 | contents: none 60 | runs-on: ubuntu-latest 61 | needs: build 62 | environment: 63 | name: 'Development' 64 | url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} 65 | 66 | steps: 67 | - name: Download artifact from build job 68 | uses: actions/download-artifact@v3 69 | with: 70 | name: node-app 71 | 72 | - name: 'Deploy to Azure WebApp' 73 | id: deploy-to-webapp 74 | uses: azure/webapps-deploy@v2 75 | with: 76 | app-name: ${{ env.AZURE_WEBAPP_NAME }} 77 | publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} 78 | package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} 79 | -------------------------------------------------------------------------------- /.github/workflows/azure-webapps-python.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and push a Python application to an Azure Web App when a commit is pushed to your default branch. 2 | # 3 | # This workflow assumes you have already created the target Azure App Service web app. 4 | # For instructions see https://docs.microsoft.com/en-us/azure/app-service/quickstart-python?tabs=bash&pivots=python-framework-flask 5 | # 6 | # To configure this workflow: 7 | # 8 | # 1. Download the Publish Profile for your Azure Web App. You can download this file from the Overview page of your Web App in the Azure Portal. 9 | # For more information: https://docs.microsoft.com/en-us/azure/app-service/deploy-github-actions?tabs=applevel#generate-deployment-credentials 10 | # 11 | # 2. Create a secret in your repository named AZURE_WEBAPP_PUBLISH_PROFILE, paste the publish profile contents as the value of the secret. 12 | # For instructions on obtaining the publish profile see: https://docs.microsoft.com/azure/app-service/deploy-github-actions#configure-the-github-secret 13 | # 14 | # 3. Change the value for the AZURE_WEBAPP_NAME. Optionally, change the PYTHON_VERSION environment variables below. 15 | # 16 | # For more information on GitHub Actions for Azure: https://github.com/Azure/Actions 17 | # For more information on the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy 18 | # For more samples to get started with GitHub Action workflows to deploy to Azure: https://github.com/Azure/actions-workflow-samples 19 | 20 | name: Build and deploy Python app to Azure Web App 21 | 22 | env: 23 | AZURE_WEBAPP_NAME: your-app-name # set this to the name of your Azure Web App 24 | PYTHON_VERSION: '3.8' # set this to the Python version to use 25 | 26 | on: 27 | push: 28 | branches: [ "main" ] 29 | workflow_dispatch: 30 | 31 | permissions: 32 | contents: read 33 | 34 | jobs: 35 | build: 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - uses: actions/checkout@v3 40 | 41 | - name: Set up Python version 42 | uses: actions/setup-python@v3.0.0 43 | with: 44 | python-version: ${{ env.PYTHON_VERSION }} 45 | cache: 'pip' 46 | 47 | - name: Create and start virtual environment 48 | run: | 49 | python -m venv venv 50 | source venv/bin/activate 51 | 52 | - name: Install dependencies 53 | run: pip install -r requirements.txt 54 | 55 | # Optional: Add step to run tests here (PyTest, Django test suites, etc.) 56 | 57 | - name: Upload artifact for deployment jobs 58 | uses: actions/upload-artifact@v3 59 | with: 60 | name: python-app 61 | path: | 62 | . 63 | !venv/ 64 | 65 | deploy: 66 | permissions: 67 | contents: none 68 | runs-on: ubuntu-latest 69 | needs: build 70 | environment: 71 | name: 'Development' 72 | url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} 73 | 74 | steps: 75 | - name: Download artifact from build job 76 | uses: actions/download-artifact@v3 77 | with: 78 | name: python-app 79 | path: . 80 | 81 | - name: 'Deploy to Azure Web App' 82 | id: deploy-to-webapp 83 | uses: azure/webapps-deploy@v2 84 | with: 85 | app-name: ${{ env.AZURE_WEBAPP_NAME }} 86 | publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} 87 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '38 10 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'csharp', 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Use only 'java' to analyze code written in Java, Kotlin or both 38 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both 39 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v3 44 | 45 | # Initializes the CodeQL tools for scanning. 46 | - name: Initialize CodeQL 47 | uses: github/codeql-action/init@v2 48 | with: 49 | languages: ${{ matrix.language }} 50 | # If you wish to specify custom queries, you can do so here or in a config file. 51 | # By default, queries listed here will override any specified in a config file. 52 | # Prefix the list here with "+" to use these queries and those in the config file. 53 | 54 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 55 | # queries: security-extended,security-and-quality 56 | 57 | 58 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 59 | # If this step fails, then you should remove it and run the build manually (see below) 60 | - name: Autobuild 61 | uses: github/codeql-action/autobuild@v2 62 | 63 | # ℹ️ Command-line programs to run using the OS shell. 64 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 65 | 66 | # If the Autobuild fails above, remove it and uncomment the following three lines. 67 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 68 | 69 | # - run: | 70 | # echo "Run, Build Application using script" 71 | # ./location_of_script_within_repo/buildscript.sh 72 | 73 | - name: Perform CodeQL Analysis 74 | uses: github/codeql-action/analyze@v2 75 | with: 76 | category: "/language:${{matrix.language}}" 77 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v3 19 | - name: 'Dependency Review' 20 | uses: actions/dependency-review-action@v2 21 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-desktop.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | # This workflow will build, test, sign and package a WPF or Windows Forms desktop application 7 | # built on .NET Core. 8 | # To learn how to migrate your existing application to .NET Core, 9 | # refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework 10 | # 11 | # To configure this workflow: 12 | # 13 | # 1. Configure environment variables 14 | # GitHub sets default environment variables for every workflow run. 15 | # Replace the variables relative to your project in the "env" section below. 16 | # 17 | # 2. Signing 18 | # Generate a signing certificate in the Windows Application 19 | # Packaging Project or add an existing signing certificate to the project. 20 | # Next, use PowerShell to encode the .pfx file using Base64 encoding 21 | # by running the following Powershell script to generate the output string: 22 | # 23 | # $pfx_cert = Get-Content '.\SigningCertificate.pfx' -Encoding Byte 24 | # [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt' 25 | # 26 | # Open the output file, SigningCertificate_Encoded.txt, and copy the 27 | # string inside. Then, add the string to the repo as a GitHub secret 28 | # and name it "Base64_Encoded_Pfx." 29 | # For more information on how to configure your signing certificate for 30 | # this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing 31 | # 32 | # Finally, add the signing certificate password to the repo as a secret and name it "Pfx_Key". 33 | # See "Build the Windows Application Packaging project" below to see how the secret is used. 34 | # 35 | # For more information on GitHub Actions, refer to https://github.com/features/actions 36 | # For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications, 37 | # refer to https://github.com/microsoft/github-actions-for-desktop-apps 38 | 39 | name: .NET Core Desktop 40 | 41 | on: 42 | push: 43 | branches: [ "main" ] 44 | pull_request: 45 | branches: [ "main" ] 46 | 47 | jobs: 48 | 49 | build: 50 | 51 | strategy: 52 | matrix: 53 | configuration: [Debug, Release] 54 | 55 | runs-on: windows-latest # For a list of available runner types, refer to 56 | # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on 57 | 58 | env: 59 | Solution_Name: your-solution-name # Replace with your solution name, i.e. MyWpfApp.sln. 60 | Test_Project_Path: your-test-project-path # Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj. 61 | Wap_Project_Directory: your-wap-project-directory-name # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package. 62 | Wap_Project_Path: your-wap-project-path # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj. 63 | 64 | steps: 65 | - name: Checkout 66 | uses: actions/checkout@v3 67 | with: 68 | fetch-depth: 0 69 | 70 | # Install the .NET Core workload 71 | - name: Install .NET Core 72 | uses: actions/setup-dotnet@v3 73 | with: 74 | dotnet-version: 6.0.x 75 | 76 | # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild 77 | - name: Setup MSBuild.exe 78 | uses: microsoft/setup-msbuild@v1.0.2 79 | 80 | # Execute all unit tests in the solution 81 | - name: Execute unit tests 82 | run: dotnet test 83 | 84 | # Restore the application to populate the obj folder with RuntimeIdentifiers 85 | - name: Restore the application 86 | run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration 87 | env: 88 | Configuration: ${{ matrix.configuration }} 89 | 90 | # Decode the base 64 encoded pfx and save the Signing_Certificate 91 | - name: Decode the pfx 92 | run: | 93 | $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}") 94 | $certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx 95 | [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte) 96 | 97 | # Create the app package by building and packaging the Windows Application Packaging project 98 | - name: Create the app package 99 | run: msbuild $env:Wap_Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }} 100 | env: 101 | Appx_Bundle: Always 102 | Appx_Bundle_Platforms: x86|x64 103 | Appx_Package_Build_Mode: StoreUpload 104 | Configuration: ${{ matrix.configuration }} 105 | 106 | # Remove the pfx 107 | - name: Remove the pfx 108 | run: Remove-Item -path $env:Wap_Project_Directory\GitHubActionsWorkflow.pfx 109 | 110 | # Upload the MSIX package: https://github.com/marketplace/actions/upload-a-build-artifact 111 | - name: Upload build artifacts 112 | uses: actions/upload-artifact@v3 113 | with: 114 | name: MSIX Package 115 | path: ${{ env.Wap_Project_Directory }}\AppPackages 116 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: .NET 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Setup .NET 20 | uses: actions/setup-dotnet@v3 21 | with: 22 | dotnet-version: 6.0.x 23 | - name: Restore dependencies 24 | run: dotnet restore 25 | - name: Build 26 | run: dotnet build --no-restore 27 | - name: Test 28 | run: dotnet test --no-build --verbosity normal 29 | -------------------------------------------------------------------------------- /.github/workflows/google-cloudrun-source.yml: -------------------------------------------------------------------------------- 1 | # This workflow will deploy source code on Cloud Run when a commit is pushed to the "main" branch 2 | # 3 | # Overview: 4 | # 5 | # 1. Authenticate to Google Cloud 6 | # 2. Deploy it to Cloud Run 7 | # 8 | # To configure this workflow: 9 | # 10 | # 1. Ensure the required Google Cloud APIs are enabled: 11 | # 12 | # Cloud Run run.googleapis.com 13 | # Cloud Build cloudbuild.googleapis.com 14 | # Artifact Registry artifactregistry.googleapis.com 15 | # 16 | # 2. Create and configure Workload Identity Federation for GitHub (https://github.com/google-github-actions/auth#setting-up-workload-identity-federation) 17 | # 18 | # 3. Ensure the required IAM permissions are granted 19 | # 20 | # Cloud Run 21 | # roles/run.admin 22 | # roles/iam.serviceAccountUser (to act as the Cloud Run runtime service account) 23 | # 24 | # Cloud Build 25 | # roles/cloudbuild.builds.editor 26 | # 27 | # Cloud Storage 28 | # roles/storage.objectAdmin 29 | # 30 | # Artifact Registry 31 | # roles/artifactregistry.admin (project or repository level) 32 | # 33 | # NOTE: You should always follow the principle of least privilege when assigning IAM roles 34 | # 35 | # 4. Create GitHub secrets for WIF_PROVIDER and WIF_SERVICE_ACCOUNT 36 | # 37 | # 5. Change the values for the SERVICE and REGION environment variables (below). 38 | # 39 | # For more support on how to run this workflow, please visit https://github.com/marketplace/actions/deploy-to-cloud-run 40 | # 41 | # Further reading: 42 | # Cloud Run runtime service account - https://cloud.google.com/run/docs/securing/service-identity 43 | # Cloud Run IAM permissions - https://cloud.google.com/run/docs/deploying-source-code#permissions_required_to_deploy 44 | # Cloud Run builds from source - https://cloud.google.com/run/docs/deploying-source-code 45 | # Principle of least privilege - https://cloud.google.com/blog/products/identity-security/dont-get-pwned-practicing-the-principle-of-least-privilege 46 | 47 | name: Deploy to Cloud Run from Source 48 | 49 | on: 50 | push: 51 | branches: [ "main" ] 52 | 53 | env: 54 | PROJECT_ID: YOUR_PROJECT_ID # TODO: update Google Cloud project id 55 | SERVICE: YOUR_SERVICE_NAME # TODO: update Cloud Run service name 56 | REGION: YOUR_SERVICE_REGION # TODO: update Cloud Run service region 57 | 58 | jobs: 59 | deploy: 60 | # Add 'id-token' with the intended permissions for workload identity federation 61 | permissions: 62 | contents: 'read' 63 | id-token: 'write' 64 | 65 | runs-on: ubuntu-latest 66 | steps: 67 | - name: Checkout 68 | uses: actions/checkout@v2 69 | 70 | - name: Google Auth 71 | id: auth 72 | uses: 'google-github-actions/auth@v0' 73 | with: 74 | workload_identity_provider: '${{ secrets.WIF_PROVIDER }}' # e.g. - projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider 75 | service_account: '${{ secrets.WIF_SERVICE_ACCOUNT }}' # e.g. - my-service-account@my-project.iam.gserviceaccount.com 76 | 77 | # NOTE: Alternative option - authentication via credentials json 78 | # - name: Google Auth 79 | # id: auth 80 | # uses: 'google-github-actions/auth@v0' 81 | # with: 82 | # credentials_json: '${{ secrets.GCP_CREDENTIALS }}' 83 | 84 | - name: Deploy to Cloud Run 85 | id: deploy 86 | uses: google-github-actions/deploy-cloudrun@v0 87 | with: 88 | service: ${{ env.SERVICE }} 89 | region: ${{ env.REGION }} 90 | # NOTE: If required, update to the appropriate source folder 91 | source: ./ 92 | 93 | # If required, use the Cloud Run url output in later steps 94 | - name: Show Output 95 | run: echo ${{ steps.deploy.outputs.url }} 96 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request_target, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: actions/first-interaction@v1 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-message: "Message that will be displayed on users' first issue" 16 | pr-message: "Message that will be displayed on users' first pull request" 17 | -------------------------------------------------------------------------------- /.github/workflows/ios.yml: -------------------------------------------------------------------------------- 1 | name: iOS starter workflow 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | name: Build and Test default scheme using any available iPhone simulator 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Set Default Scheme 18 | run: | 19 | scheme_list=$(xcodebuild -list -json | tr -d "\n") 20 | default=$(echo $scheme_list | ruby -e "require 'json'; puts JSON.parse(STDIN.gets)['project']['targets'][0]") 21 | echo $default | cat >default 22 | echo Using default scheme: $default 23 | - name: Build 24 | env: 25 | scheme: ${{ 'default' }} 26 | platform: ${{ 'iOS Simulator' }} 27 | run: | 28 | # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) 29 | device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"` 30 | if [ $scheme = default ]; then scheme=$(cat default); fi 31 | if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi 32 | file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` 33 | xcodebuild build-for-testing -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device" 34 | - name: Test 35 | env: 36 | scheme: ${{ 'default' }} 37 | platform: ${{ 'iOS Simulator' }} 38 | run: | 39 | # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) 40 | device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"` 41 | if [ $scheme = default ]; then scheme=$(cat default); fi 42 | if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi 43 | file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` 44 | xcodebuild test-without-building -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device" 45 | -------------------------------------------------------------------------------- /.github/workflows/jekyll-docker.yml: -------------------------------------------------------------------------------- 1 | name: Jekyll site CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Build the site in the jekyll/builder container 17 | run: | 18 | docker run \ 19 | -v ${{ github.workspace }}:/srv/jekyll -v ${{ github.workspace }}/_site:/srv/jekyll/_site \ 20 | jekyll/builder:latest /bin/bash -c "chmod -R 777 /srv/jekyll && jekyll build --future" 21 | -------------------------------------------------------------------------------- /.github/workflows/label.yml: -------------------------------------------------------------------------------- 1 | # This workflow will triage pull requests and apply a label based on the 2 | # paths that are modified in the pull request. 3 | # 4 | # To use this workflow, you will need to set up a .github/labeler.yml 5 | # file with configuration. For more information, see: 6 | # https://github.com/actions/labeler 7 | 8 | name: Labeler 9 | on: [pull_request_target] 10 | 11 | jobs: 12 | label: 13 | 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | pull-requests: write 18 | 19 | steps: 20 | - uses: actions/labeler@v4 21 | with: 22 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 23 | -------------------------------------------------------------------------------- /.github/workflows/manual.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow that is manually triggered 2 | 3 | name: Manual workflow 4 | 5 | # Controls when the action will run. Workflow runs when manually triggered using the UI 6 | # or API. 7 | on: 8 | workflow_dispatch: 9 | # Inputs the workflow accepts. 10 | inputs: 11 | name: 12 | # Friendly description to be shown in the UI instead of 'name' 13 | description: 'Person to greet' 14 | # Default value if no value is explicitly provided 15 | default: 'World' 16 | # Input has to be provided for the workflow to run 17 | required: true 18 | 19 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 20 | jobs: 21 | # This workflow contains a single job called "greet" 22 | greet: 23 | # The type of runner that the job will run on 24 | runs-on: ubuntu-latest 25 | 26 | # Steps represent a sequence of tasks that will be executed as part of the job 27 | steps: 28 | # Runs a single command using the runners shell 29 | - name: Send greeting 30 | run: echo "Hello ${{ github.event.inputs.name }}" 31 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [14.x, 16.x, 18.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm test 32 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish-github-packages.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | - run: npm ci 19 | - run: npm test 20 | 21 | publish-gpr: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | permissions: 25 | contents: read 26 | packages: write 27 | steps: 28 | - uses: actions/checkout@v3 29 | - uses: actions/setup-node@v3 30 | with: 31 | node-version: 16 32 | registry-url: https://npm.pkg.github.com/ 33 | - run: npm ci 34 | - run: npm publish 35 | env: 36 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 37 | -------------------------------------------------------------------------------- /.github/workflows/objective-c-xcode.yml: -------------------------------------------------------------------------------- 1 | name: Xcode - Build and Analyze 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | name: Build and analyse default scheme using xcodebuild command 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Set Default Scheme 18 | run: | 19 | scheme_list=$(xcodebuild -list -json | tr -d "\n") 20 | default=$(echo $scheme_list | ruby -e "require 'json'; puts JSON.parse(STDIN.gets)['project']['targets'][0]") 21 | echo $default | cat >default 22 | echo Using default scheme: $default 23 | - name: Build 24 | env: 25 | scheme: ${{ 'default' }} 26 | run: | 27 | if [ $scheme = default ]; then scheme=$(cat default); fi 28 | if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi 29 | file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` 30 | xcodebuild clean build analyze -scheme "$scheme" -"$filetype_parameter" "$file_to_build" | xcpretty && exit ${PIPESTATUS[0]} 31 | -------------------------------------------------------------------------------- /.github/workflows/python-package-conda.yml: -------------------------------------------------------------------------------- 1 | name: Python Package using Conda 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build-linux: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | max-parallel: 5 10 | 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Set up Python 3.10 14 | uses: actions/setup-python@v3 15 | with: 16 | python-version: '3.10' 17 | - name: Add conda to system path 18 | run: | 19 | # $CONDA is an environment variable pointing to the root of the miniconda directory 20 | echo $CONDA/bin >> $GITHUB_PATH 21 | - name: Install dependencies 22 | run: | 23 | conda env update --file environment.yml --name base 24 | - name: Lint with flake8 25 | run: | 26 | conda install flake8 27 | # stop the build if there are Python syntax errors or undefined names 28 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 29 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 30 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 31 | - name: Test with pytest 32 | run: | 33 | conda install pytest 34 | pytest 35 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Python 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: '3.x' 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install build 33 | - name: Build package 34 | run: python -m build 35 | - name: Publish package 36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 37 | with: 38 | user: __token__ 39 | password: ${{ secrets.PYPI_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | - cron: '27 17 * * *' 11 | 12 | jobs: 13 | stale: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | issues: write 18 | pull-requests: write 19 | 20 | steps: 21 | - uses: actions/stale@v5 22 | with: 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | stale-issue-message: 'Stale issue message' 25 | stale-pr-message: 'Stale pull request message' 26 | stale-issue-label: 'no-issue-activity' 27 | stale-pr-label: 'no-pr-activity' 28 | -------------------------------------------------------------------------------- /.github/workflows/tencent.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a docker container, publish and deploy it to Tencent Kubernetes Engine (TKE) when there is a push to the "main" branch. 2 | # 3 | # To configure this workflow: 4 | # 5 | # 1. Ensure that your repository contains the necessary configuration for your Tencent Kubernetes Engine cluster, 6 | # including deployment.yml, kustomization.yml, service.yml, etc. 7 | # 8 | # 2. Set up secrets in your workspace: 9 | # - TENCENT_CLOUD_SECRET_ID with Tencent Cloud secret id 10 | # - TENCENT_CLOUD_SECRET_KEY with Tencent Cloud secret key 11 | # - TENCENT_CLOUD_ACCOUNT_ID with Tencent Cloud account id 12 | # - TKE_REGISTRY_PASSWORD with TKE registry password 13 | # 14 | # 3. Change the values for the TKE_IMAGE_URL, TKE_REGION, TKE_CLUSTER_ID and DEPLOYMENT_NAME environment variables (below). 15 | 16 | name: Tencent Kubernetes Engine 17 | 18 | on: 19 | push: 20 | branches: [ "main" ] 21 | 22 | # Environment variables available to all jobs and steps in this workflow 23 | env: 24 | TKE_IMAGE_URL: ccr.ccs.tencentyun.com/demo/mywebapp 25 | TKE_REGION: ap-guangzhou 26 | TKE_CLUSTER_ID: cls-mywebapp 27 | DEPLOYMENT_NAME: tke-test 28 | 29 | permissions: 30 | contents: read 31 | 32 | jobs: 33 | setup-build-publish-deploy: 34 | name: Setup, Build, Publish, and Deploy 35 | runs-on: ubuntu-latest 36 | environment: production 37 | steps: 38 | 39 | - name: Checkout 40 | uses: actions/checkout@v3 41 | 42 | # Build 43 | - name: Build Docker image 44 | run: | 45 | docker build -t ${TKE_IMAGE_URL}:${GITHUB_SHA} . 46 | 47 | - name: Login TKE Registry 48 | run: | 49 | docker login -u ${{ secrets.TENCENT_CLOUD_ACCOUNT_ID }} -p '${{ secrets.TKE_REGISTRY_PASSWORD }}' ${TKE_IMAGE_URL} 50 | 51 | # Push the Docker image to TKE Registry 52 | - name: Publish 53 | run: | 54 | docker push ${TKE_IMAGE_URL}:${GITHUB_SHA} 55 | 56 | - name: Set up Kustomize 57 | run: | 58 | curl -o kustomize --location https://github.com/kubernetes-sigs/kustomize/releases/download/v3.1.0/kustomize_3.1.0_linux_amd64 59 | chmod u+x ./kustomize 60 | 61 | - name: Set up ~/.kube/config for connecting TKE cluster 62 | uses: TencentCloud/tke-cluster-credential-action@v1 63 | with: 64 | secret_id: ${{ secrets.TENCENT_CLOUD_SECRET_ID }} 65 | secret_key: ${{ secrets.TENCENT_CLOUD_SECRET_KEY }} 66 | tke_region: ${{ env.TKE_REGION }} 67 | cluster_id: ${{ env.TKE_CLUSTER_ID }} 68 | 69 | - name: Switch to TKE context 70 | run: | 71 | kubectl config use-context ${TKE_CLUSTER_ID}-context-default 72 | 73 | # Deploy the Docker image to the TKE cluster 74 | - name: Deploy 75 | run: | 76 | ./kustomize edit set image ${TKE_IMAGE_URL}:${GITHUB_SHA} 77 | ./kustomize build . | kubectl apply -f - 78 | kubectl rollout status deployment/${DEPLOYMENT_NAME} 79 | kubectl get services -o wide 80 | -------------------------------------------------------------------------------- /.github/workflows/webpack.yml: -------------------------------------------------------------------------------- 1 | name: NodeJS with Webpack 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x, 16.x, 18.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | 25 | - name: Build 26 | run: | 27 | npm install 28 | npx webpack 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /.vs/ 6 | .vs/ 7 | /.vs/ 8 | *bin/ 9 | *obj/ 10 | *.pdb 11 | *.dll 12 | *.zip 13 | 14 | *Debug/ 15 | 16 | *_i.c 17 | *_p.c 18 | *_i.h 19 | *.ilk 20 | *.meta 21 | *.obj 22 | *.pch 23 | *.pdb 24 | *.pgc 25 | *.pgd 26 | *.rsp 27 | *.sbr 28 | *.tlb 29 | *.tli 30 | *.tlh 31 | *.tmp 32 | *.tmp_proj 33 | *.log 34 | *.vspscc 35 | *.vssscc 36 | .builds 37 | *.pidb 38 | *.svclog 39 | *.scc 40 | 41 | *.suo 42 | *.vsidx 43 | desktop.ini 44 | /desktop.ini 45 | 46 | bundleconfig.json 47 | ReadmeInternal.md 48 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff", 8 | "useMendCheckNames": true 9 | }, 10 | "issueSettings": { 11 | "minSeverityLevel": "LOW", 12 | "issueType": "DEPENDENCY" 13 | } 14 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | PiOS License 2 | 3 | Copyright (C) 4 | 5 | Permission is hereby granted by the application software developer (“Software Developer”), free 6 | of charge, to any person obtaining a copy of this application, software and associated 7 | documentation files (the “Software”), which was developed by the Software Developer for use on 8 | Pi Network, whereby the purpose of this license is to permit the development of derivative works 9 | based on the Software, including the right to use, copy, modify, merge, publish, distribute, 10 | sub-license, and/or sell copies of such derivative works and any Software components incorporated 11 | therein, and to permit persons to whom such derivative works are furnished to do so, in each case, 12 | solely to develop, use and market applications for the official Pi Network. For purposes of this 13 | license, Pi Network shall mean any application, software, or other present or future platform 14 | developed, owned or managed by Pi Community Company, and its parents, affiliates or subsidiaries, 15 | for which the Software was developed, or on which the Software continues to operate. However, 16 | you are prohibited from using any portion of the Software or any derivative works thereof in any 17 | manner (a) which infringes on any Pi Network intellectual property rights, (b) to hack any of Pi 18 | Network’s systems or processes or (c) to develop any product or service which is competitive with 19 | the Pi Network. 20 | 21 | The above copyright notice and this permission notice shall be included in all copies or 22 | substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 25 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 26 | AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS, PUBLISHERS, OR COPYRIGHT HOLDERS OF THIS 27 | SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL 28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO BUSINESS INTERRUPTION, LOSS OF USE, DATA OR PROFITS) 29 | HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 30 | TORT (INCLUDING NEGLIGENCE) ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 31 | OR OTHER DEALINGS IN THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 32 | 33 | Pi, Pi Network and the Pi logo are trademarks of the Pi Community Company. 34 | -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/Dto/Auth/AuthMe.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace PiNetwork.Blazor.Sdk.Dto.Auth; 4 | 5 | public sealed class AuthMeDto 6 | { 7 | [JsonPropertyName("uid")] 8 | public string Uid { get; set; } 9 | 10 | [JsonPropertyName("username")] 11 | public string Username { get; set; } 12 | 13 | [JsonPropertyName("credentials")] 14 | public Credentials Credentials { get; set; } 15 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/Dto/Auth/AuthMe.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid": "4be92209-125d-4a5c-b3f8-a094a615daa4", 3 | "credentials": { 4 | "scopes": [ "payments", "username", "wallet_address", "platform" ], 5 | "valid_until": { 6 | "timestamp": 1698224114, 7 | "iso8601": "2023-10-25T08:55:14Z" 8 | } 9 | }, 10 | "username": "username" 11 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/Dto/Auth/AuthResultDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace PiNetwork.Blazor.Sdk.Dto.Auth; 5 | 6 | public sealed class AuthResultDto 7 | { 8 | [JsonPropertyName("accessToken")] 9 | public string AccessToken { get; set; } 10 | 11 | [JsonPropertyName("user")] 12 | public User User { get; set; } 13 | } 14 | 15 | public sealed class User 16 | { 17 | [JsonPropertyName("uid")] 18 | public string Uid { get; set; } 19 | 20 | [JsonPropertyName("username")] 21 | public string Username { get; set; } 22 | 23 | [JsonPropertyName("credentials")] 24 | public Credentials Credentials { get; set; } 25 | } 26 | 27 | public sealed class Credentials 28 | { 29 | [JsonPropertyName("scopes")] 30 | public string[] Scopes { get; set; } 31 | 32 | [JsonPropertyName("valid_until")] 33 | public ValidUntil ValidUntil { get; set; } 34 | } 35 | 36 | public sealed class ValidUntil 37 | { 38 | [JsonPropertyName("timestamp")] 39 | public Int64 TimeStamp { get; set; } 40 | 41 | [JsonPropertyName("iso8601")] 42 | public string Iso8601 { get; set; } 43 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/Dto/Auth/AuthResultDto.json: -------------------------------------------------------------------------------- 1 | { 2 | "accessToken": "string", 3 | "user": { 4 | "uid": "string", 5 | "username": "string", 6 | "credentials": { 7 | "scopes": "Array", // a list of granted scopes 8 | "valid_until": { 9 | "timestamp": "number", 10 | "iso8601": "string" 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/Dto/Payment/PaymentCompleteDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace PiNetwork.Blazor.Sdk.Dto.Payment; 4 | 5 | public sealed class PaymentCompleteDto 6 | { 7 | [JsonPropertyName("txid")] 8 | public string Txid { get; set; } 9 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/Dto/Payment/PaymentDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace PiNetwork.Blazor.Sdk.Dto.Payment; 4 | 5 | public sealed class PaymentDto 6 | { 7 | [JsonPropertyName("identifier")] 8 | public string Identifier { get; set; } 9 | 10 | [JsonPropertyName("user_uid")] 11 | public string UserUid { get; set; } 12 | 13 | [JsonPropertyName("amount")] 14 | public double Amount { get; set; } 15 | 16 | [JsonPropertyName("memo")] 17 | public string Memo { get; set; } 18 | 19 | [JsonPropertyName("metadata")] 20 | public Metadata Metadata { get; set; } 21 | 22 | [JsonPropertyName("from_address")] 23 | public string FromAddress { get; set; } 24 | 25 | [JsonPropertyName("to_address")] 26 | public string ToAddress { get; set; } 27 | 28 | [JsonPropertyName("direction")] 29 | public string Direction { get; set; } 30 | 31 | [JsonPropertyName("created_at")] 32 | public string CreatedAt { get; set; } 33 | 34 | [JsonPropertyName("network")] 35 | public string Network { get; set; } 36 | 37 | [JsonPropertyName("status")] 38 | public Status Status { get; set; } 39 | 40 | [JsonPropertyName("transaction")] 41 | public Transaction Transaction { get; set; } 42 | } 43 | 44 | public sealed class Metadata 45 | { 46 | [JsonPropertyName("orderId")] 47 | public int OrderId { get; set; } 48 | } 49 | 50 | public sealed class Status 51 | { 52 | [JsonPropertyName("developer_approved")] 53 | public bool DeveloperApproved { get; set; } 54 | 55 | [JsonPropertyName("transaction_verified")] 56 | public bool TransactionVerified { get; set; } 57 | 58 | [JsonPropertyName("developer_completed")] 59 | public bool DeveloperCompleted { get; set; } 60 | 61 | [JsonPropertyName("cancelled")] 62 | public bool Cancelled { get; set; } 63 | 64 | [JsonPropertyName("user_cancelled")] 65 | public bool UserCancelled { get; set; } 66 | } 67 | 68 | public sealed class Transaction 69 | { 70 | [JsonPropertyName("txid")] 71 | public string Txid { get; set; } 72 | 73 | [JsonPropertyName("verified")] 74 | public bool Verified { get; set; } 75 | 76 | [JsonPropertyName("_link")] 77 | public string Link { get; set; } 78 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/Dto/Payment/PaymentDto.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "string", 3 | "user_uid": "string", 4 | "amount": 3.14, 5 | "memo": "string", 6 | "metadata": { 7 | "orderId": 123, 8 | "chalangesIds": [ 1, 2, 3 ] 9 | }, 10 | "from_address": "string", 11 | "to_address": "string", 12 | "direction": "string", 13 | "created_at": "string", 14 | "network": "string", 15 | "status": { 16 | "developer_approved": true, 17 | "transaction_verified": true, 18 | "developer_completed": true, 19 | "cancelled": true, 20 | "user_cancelled": true 21 | }, 22 | "transaction": { 23 | "txid": "string", 24 | "verified": true, 25 | "_link": "string" 26 | } 27 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/Dto/TransactionA2UView.cs: -------------------------------------------------------------------------------- 1 | namespace PiNetwork.Blazor.Sdk.Dto; 2 | 3 | public sealed record TransactionA2UView(double Amount, string Memmo, string FromAddress, string ToAddress); 4 | -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/Javascript/PiNetworkCallJavascript.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | using System.Threading.Tasks; 3 | 4 | namespace PiNetwork.Blazor.Sdk.Javascript; 5 | 6 | public static class PiNetworkCallJavascript 7 | { 8 | public static ValueTask Authenticate(IJSRuntime jsRuntime, DotNetObjectReference reference, string redirectUri, int retries) 9 | => jsRuntime.InvokeVoidAsync("PiNetworkBlazorSdk.Authenticate", reference, redirectUri, retries); 10 | 11 | public static ValueTask CreatePayment(IJSRuntime jsRuntime, DotNetObjectReference reference, decimal amount, string memo, int orderId) 12 | => jsRuntime.InvokeVoidAsync("PiNetworkBlazorSdk.CreatePayment", reference, amount, memo, orderId); 13 | 14 | public static ValueTask OpenShareDialog(IJSRuntime jsRuntime, string title, string message) 15 | => jsRuntime.InvokeVoidAsync("PiNetworkBlazorSdk.OpenShareDialog", title, message); 16 | 17 | public static ValueTask IsPiNetworkBrowser(IJSRuntime jsRuntime, DotNetObjectReference reference) 18 | => jsRuntime.InvokeVoidAsync("Browser.IsPiNetworkBrowser", reference); 19 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/Pages/PiNetworkMain.razor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KOSASIH/pi-blazor/f27300869f8f81c894528e060ff984ac06c25893/PiNetwork.Blazor.Sdk/Pages/PiNetworkMain.razor -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/Pages/PiNetworkMain.razor.cs: -------------------------------------------------------------------------------- 1 | using Blazored.SessionStorage; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.JSInterop; 5 | using System.Threading.Tasks; 6 | 7 | namespace PiNetwork.Blazor.Sdk.Pages; 8 | 9 | public partial class PiNetworkMain : ComponentBase 10 | { 11 | private readonly ILogger logger; 12 | private readonly NavigationManager navigationManager; 13 | private readonly IPiNetworkClientBlazor sdk; 14 | private readonly ISessionStorageService sessionStorage; 15 | 16 | public PiNetworkMain(NavigationManager navigationManager, IPiNetworkClientBlazor sdk, ISessionStorageService sessionStorage) 17 | { 18 | this.navigationManager = navigationManager; 19 | this.sdk = sdk; 20 | this.sessionStorage = sessionStorage; 21 | } 22 | 23 | public PiNetworkMain(NavigationManager navigationManager, IPiNetworkClientBlazor sdk, ISessionStorageService sessionStorage, ILoggerFactory loggerFactory) 24 | : this(navigationManager, sdk, sessionStorage) 25 | { 26 | this.logger = loggerFactory.CreateLogger(this.GetType().Name); 27 | } 28 | 29 | [JSInvokable] 30 | public async Task AuthenticateOnSuccess(string userString, string redirectUri) 31 | { 32 | if (this.logger is { }) 33 | this.logger.LogInformation("Method: {@Method}. Authentication: {@Authentication}. RedirectUri: {RedirectUri}", nameof(AuthenticateOnSuccess), userString, redirectUri); 34 | 35 | await this.sdk.AuthenticateOnSuccessCallBack(userString, redirectUri); 36 | } 37 | 38 | [JSInvokable] 39 | public async void AuthenticateOnError(string error, string redirectUri) 40 | { 41 | if (this.logger is { }) 42 | { 43 | if (string.IsNullOrEmpty(error) || error.Equals("{}")) 44 | this.logger.LogWarning("Method: {@Method}. Error: {@error}. Redirect: {@redirect}", nameof(AuthenticateOnError), error, redirectUri); 45 | else 46 | this.logger.LogError("Method: {@Method}. Error: {@error}. Redirect: {@redirect}", nameof(AuthenticateOnError), error, redirectUri); 47 | } 48 | 49 | await this.sdk.AuthenticateOnErrorCallBack(error, redirectUri, 0); 50 | } 51 | 52 | [JSInvokable] 53 | public async Task CreatePaymentOnIncopletePaymentFound(string identifier, string txid) 54 | { 55 | if (this.logger is { }) 56 | this.logger.LogInformation("Method: {@Method}. Identifier: {@Identifier}. TxId: {@TxId}", nameof(CreatePaymentOnIncopletePaymentFound), identifier, txid); 57 | 58 | await this.sdk.CreatePaymentOnReadyForServerCompletionCallBack(identifier, txid); 59 | } 60 | 61 | [JSInvokable] 62 | public async Task CreatePaymentOnReadyForServerApproval(string paymentId) 63 | { 64 | if (this.logger is { }) 65 | this.logger.LogInformation("Method: {@Method}. PaymentId: {PaymentId}", nameof(CreatePaymentOnReadyForServerApproval), paymentId); 66 | 67 | await this.sdk.CreatePaymentOnReadyForServerApprovalCallBack(paymentId); 68 | } 69 | 70 | [JSInvokable] 71 | public async Task CreatePaymentOnReadyForServerCompletion(string paymentId, string txid) 72 | { 73 | if (this.logger is { }) 74 | this.logger.LogInformation("Method: {@Method}. PaymentId: {@PaymentId}", nameof(CreatePaymentOnReadyForServerCompletion), paymentId); 75 | 76 | await this.sdk.CreatePaymentOnReadyForServerCompletionCallBack(paymentId, txid); 77 | } 78 | 79 | [JSInvokable] 80 | public async Task CreatePaymentOnCancel(string paymentId) 81 | { 82 | if (this.logger is { }) 83 | this.logger.LogInformation("Method: {@Method}. PaymentId: {PaymentId}", nameof(CreatePaymentOnCancel), paymentId); 84 | 85 | await this.sdk.CreatePaymentOnCancelCallBack(paymentId); 86 | } 87 | 88 | [JSInvokable] 89 | public async Task CreatePaymentOnError(string identifier, string txid) 90 | { 91 | if (this.logger is { }) 92 | this.logger.LogWarning("Method: {@Method}. Identifier: {@identifier}. TxId: {@txid}", nameof(CreatePaymentOnError), identifier, txid); 93 | 94 | await this.sdk.CreatePaymentOnErrorCallBack(identifier, txid); 95 | } 96 | 97 | [JSInvokable] 98 | public async Task IsPiNetworkBrowser() 99 | { 100 | /* 101 | if (this.logger is { }) 102 | this.logger.LogInformation("Method: {@Method}. {@Value}", nameof(IsPiNetworkBrowser), true); 103 | */ 104 | await this.sessionStorage.SetItemAsStringAsync(Common.PiNetworkConstants.IsPiNetworkBrowser, "1"); 105 | } 106 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/PiNetwork.Blazor.Sdk.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | true 6 | 1.1.1 7 | AnyCPU;x86 8 | Pi Network SDK 9 | Arturas Valincius 10 | True 11 | PiNetwork.Blazor.Sdk 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/PiNetworkA2UServerBlazor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using PiNetwork.Blazor.Sdk.Common; 3 | using PiNetwork.Blazor.Sdk.Dto; 4 | using PiNetwork.Blazor.Sdk.Dto.Auth; 5 | using stellar_dotnet_sdk; 6 | using stellar_dotnet_sdk.responses; 7 | using System; 8 | using System.Net.Http; 9 | using System.Net.Http.Headers; 10 | using System.Net.Http.Json; 11 | using System.Threading.Tasks; 12 | 13 | namespace PiNetwork.Blazor.Sdk; 14 | 15 | public interface IPiNetworkA2UServerBlazor 16 | { 17 | Task GetAccountNativeBalance(string network, string account); 18 | Task MeGet(string accessToken); 19 | Task SendNativeAssets(string network, string seed, TransactionA2UView data, uint fee = 100000); 20 | } 21 | 22 | public sealed class PiNetworkA2UServerBlazor : IPiNetworkA2UServerBlazor 23 | { 24 | private readonly ILogger logger; 25 | private readonly HttpClient httpClient; 26 | 27 | public PiNetworkA2UServerBlazor(ILoggerFactory loggerFactory, IHttpClientFactory clientFactory) 28 | { 29 | this.logger = loggerFactory.CreateLogger(this.GetType().Name); 30 | this.httpClient = clientFactory.CreateClient(PiNetworkConstants.PiNetworkClient); 31 | } 32 | 33 | /// 34 | /// Get user auth information 35 | /// 36 | /// from user authentication responce 37 | /// 38 | public async Task MeGet(string accessToken) 39 | { 40 | try 41 | { 42 | this.logger.LogInformation("Method: {@Method}. AccessToken: {@accessToken}", nameof(MeGet), accessToken); 43 | 44 | this.httpClient.DefaultRequestHeaders.Add("Accept", $"application/json"); 45 | this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); 46 | 47 | var result = await this.httpClient.GetFromJsonAsync($"me"); 48 | 49 | return result; 50 | } 51 | catch (Exception e) 52 | { 53 | this.logger.LogError(e, "Method: {@Method}. accessToken: {@AccessToken}", nameof(AuthMeDto), accessToken); 54 | throw; 55 | } 56 | } 57 | 58 | /// 59 | /// Get native balance 'pi network balance' 60 | /// 61 | /// 62 | /// 63 | /// 64 | public async Task GetAccountNativeBalance(string network, string account) 65 | { 66 | try 67 | { 68 | Server server = GetServerAsync(network); 69 | KeyPair keypair; 70 | 71 | if (account.StartsWith("S")) 72 | keypair = KeyPair.FromSecretSeed(account); 73 | else 74 | keypair = KeyPair.FromAccountId(account); 75 | 76 | AccountResponse accountResponse = await server.Accounts.Account(keypair.AccountId); 77 | 78 | Balance[] balances = accountResponse.Balances; 79 | 80 | for (int i = 0; i < balances.Length; i++) 81 | { 82 | if (balances[i].AssetType == "native") 83 | return double.Parse(balances[i].BalanceString); 84 | } 85 | 86 | return 0.0; 87 | } 88 | catch (Exception e) 89 | { 90 | this.logger.LogError(e, "Method: {@Method}. Network: {@Network}, Account: {@Account}", nameof(GetAccountNativeBalance), network, account); 91 | throw; 92 | } 93 | } 94 | 95 | /// 96 | /// 97 | /// 98 | /// "Pi Network" or "Pi Testnet" 99 | /// App developer seed 100 | /// transaction data 101 | /// transaction fee 102 | /// 103 | /// 104 | public async Task SendNativeAssets(string network, string seed, TransactionA2UView data, uint fee = 100000) 105 | { 106 | var sourceKeypair = KeyPair.FromSecretSeed(seed); 107 | Server server = GetServerAsync(network); 108 | var destinationKeyPair = KeyPair.FromAccountId(data.ToAddress); 109 | AccountResponse sourceAccountResponse = await server.Accounts.Account(sourceKeypair.AccountId); 110 | var sourceAccount = new Account(sourceKeypair.AccountId, sourceAccountResponse.SequenceNumber); 111 | Asset asset = new AssetTypeNative(); 112 | 113 | double balance = 0.0; 114 | for (int i = 0; i < sourceAccountResponse.Balances.Length; i++) 115 | { 116 | Balance ast = sourceAccountResponse.Balances[i]; 117 | if (ast.AssetType == "native") 118 | { 119 | if (double.TryParse(ast.BalanceString, out balance)) 120 | break; 121 | } 122 | } 123 | if (balance < data.Amount + 0.01) 124 | { 125 | throw new Exception($"Not enough balance ({balance})"); 126 | } 127 | 128 | string amount = $"{Math.Floor(data.Amount * 10000000.0) / 10000000.0:F7}"; 129 | try 130 | { 131 | var operation = new PaymentOperation.Builder(destinationKeyPair, asset, amount) 132 | .SetSourceAccount(sourceAccount.KeyPair) 133 | .Build(); 134 | 135 | var Identifier = string.IsNullOrEmpty(data.Memmo) ? $"" : $"{data.Memmo.Trim()}"; 136 | var memo = new MemoText(string.IsNullOrEmpty(Identifier) ? $"" : Identifier.Substring(0, Math.Min(Identifier.Length, 28))); 137 | var transaction = new TransactionBuilder(sourceAccount) 138 | .AddOperation(operation) 139 | .AddMemo(memo) 140 | .SetFee(fee) 141 | .Build(); 142 | 143 | transaction.Sign(sourceKeypair, new Network(network)); 144 | 145 | var tx = await server.SubmitTransaction(transaction); 146 | 147 | return tx; 148 | } 149 | catch (Exception e) 150 | { 151 | this.logger.LogError(e, "Method: {@Method}. Network: {@Network}, Data: {@Data}", nameof(SendNativeAssets), network, data); 152 | throw; 153 | } 154 | } 155 | 156 | private static Server GetServerAsync(string network) 157 | { 158 | if (network == PiNetworkConstants.PiNetwork) 159 | return new Server("https://api.mainnet.minepi.com"); 160 | if (network == PiNetworkConstants.PiTestnet) 161 | return new Server("https://api.testnet.minepi.com"); 162 | 163 | throw new PiNetworkException($"Incorect server name provided: {network}"); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/PiNetworkClientBlazor.cs: -------------------------------------------------------------------------------- 1 | using Blazored.SessionStorage; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.JSInterop; 5 | using PiNetwork.Blazor.Sdk.Javascript; 6 | using PiNetwork.Blazor.Sdk.Pages; 7 | using System; 8 | using System.Threading.Tasks; 9 | 10 | namespace PiNetwork.Blazor.Sdk; 11 | 12 | public interface IPiNetworkClientBlazor 13 | { 14 | Task Authenticate(string redirectUri, int retries = 0); 15 | 16 | Task AuthenticateOnErrorCallBack(string error, string redirectUri, int retries = 0); 17 | 18 | Task AuthenticateOnSuccessCallBack(string username, string redirectUri); 19 | 20 | Task CreatePayment(decimal amount, string memo, int orderId, int retries = 0); 21 | 22 | Task CreatePaymentOnReadyForServerApprovalCallBack(string paymentId, int retries = 0); 23 | 24 | Task CreatePaymentOnReadyForServerCompletionCallBack(string paymentId, string txid, int retries = 0); 25 | 26 | Task CreatePaymentOnCancelCallBack(string paymentId); 27 | 28 | Task CreatePaymentOnErrorCallBack(string paymentId, string txid); 29 | 30 | Task OpenShareDialog(string title, string messsage); 31 | 32 | Task IsPiNetworkBrowser(); 33 | } 34 | 35 | public abstract class PiNetworkClientBlazor : IPiNetworkClientBlazor 36 | { 37 | private readonly ILoggerFactory loggerFactory; 38 | 39 | private readonly ILogger logger; 40 | protected readonly NavigationManager navigationManager; 41 | protected readonly IJSRuntime jsRuntime; 42 | private readonly ISessionStorageService sessionStorage; 43 | 44 | /// 45 | /// Number of retries to make if error / message content is usafull for making retry. 46 | /// 47 | public virtual int Retries { get; set; } = 10; 48 | 49 | /// 50 | /// Delay in milliseconds 51 | /// 52 | public virtual int RetryDelay { get; set; } = 1000; 53 | 54 | public PiNetworkClientBlazor(NavigationManager navigationManager, IJSRuntime jsRuntime, ISessionStorageService sessionStorage) 55 | { 56 | this.jsRuntime = jsRuntime; 57 | this.navigationManager = navigationManager; 58 | this.sessionStorage = sessionStorage; 59 | } 60 | 61 | public PiNetworkClientBlazor(NavigationManager navigationManager, IJSRuntime jsRuntime, ISessionStorageService sessionStorage, ILoggerFactory loggerFactory) 62 | : this(navigationManager, jsRuntime, sessionStorage) 63 | { 64 | this.loggerFactory = loggerFactory; 65 | this.logger = loggerFactory.CreateLogger(this.GetType().Name); 66 | } 67 | 68 | public virtual async Task Authenticate(string redirectUri, int retries = 0) 69 | { 70 | if (this.logger is { }) 71 | this.logger.LogInformation("Method: {@Method}", nameof(Authenticate)); 72 | 73 | DotNetObjectReference objRef; 74 | 75 | if (this.logger is { }) 76 | objRef = DotNetObjectReference.Create(new PiNetworkMain(this.navigationManager, this, this.sessionStorage, this.loggerFactory)); 77 | else 78 | objRef = DotNetObjectReference.Create(new PiNetworkMain(this.navigationManager, this, this.sessionStorage)); 79 | 80 | try 81 | { 82 | await PiNetworkCallJavascript.Authenticate(jsRuntime, objRef, redirectUri, retries); 83 | } 84 | catch (Exception e) 85 | { 86 | if (this.logger is { }) 87 | this.logger.LogError(e, "Method: {@Method}. Message: {Message}", nameof(Authenticate), e.Message); 88 | 89 | await this.sessionStorage.SetItemAsync(Common.PiNetworkConstants.PiNetworkSdkCallBackError, Common.Messages.AuthenticationError); 90 | 91 | this.navigationManager.NavigateTo($"/", forceLoad: true); 92 | } 93 | } 94 | 95 | public abstract Task AuthenticateOnErrorCallBack(string error, string redirectUri, int retries = 0); 96 | 97 | public abstract Task AuthenticateOnSuccessCallBack(string username, string redirectUri); 98 | 99 | /// 100 | /// Make payment 101 | /// 102 | /// amount to charge 103 | /// text to display. Limit to 25 characters 104 | /// your order id 105 | /// 0 for first attempt 106 | /// 107 | public virtual async Task CreatePayment(decimal amount, string memo, int orderId, int retries = 0) 108 | { 109 | DotNetObjectReference objRef; 110 | 111 | if (this.logger is { }) 112 | objRef = DotNetObjectReference.Create(new PiNetworkMain(this.navigationManager, this, this.sessionStorage, this.loggerFactory)); 113 | else 114 | objRef = DotNetObjectReference.Create(new PiNetworkMain(navigationManager, this, sessionStorage)); 115 | 116 | try 117 | { 118 | await PiNetworkCallJavascript.CreatePayment(jsRuntime, objRef, amount, memo, orderId); 119 | } 120 | catch (TaskCanceledException e) 121 | { 122 | if (this.logger is { }) 123 | this.logger.LogWarning(e, "Method: {@Method}. Message: {Message}", nameof(CreatePayment), e.Message); 124 | } 125 | catch (Exception e) 126 | { 127 | //Some exceptions can be practical to retrie. They can be added here. Max 3 retries for client side. 128 | if (e is JSException) 129 | { 130 | string textToSearch = "Cannot create a payment without \"payments\" scope"; 131 | 132 | if (CreatePaymentExceptionMessageValidation(e, textToSearch) && retries <= this.Retries) 133 | { 134 | await Task.Delay(this.RetryDelay); 135 | await CreatePayment(amount, memo, orderId, retries + 1); 136 | } 137 | else 138 | await CreatePaymentExceptionProccess(e, textToSearch); 139 | } 140 | else 141 | { 142 | if (this.logger is { }) 143 | this.logger.LogError(e, "Method: {@Method}. Message: {Message}", nameof(CreatePayment), e.Message); 144 | 145 | await this.sessionStorage.SetItemAsync(Common.PiNetworkConstants.PiNetworkSdkCallBackError, Common.Messages.PaymentError); 146 | 147 | this.navigationManager.NavigateTo($"/", forceLoad: true); 148 | } 149 | } 150 | } 151 | 152 | public abstract Task CreatePaymentOnReadyForServerApprovalCallBack(string paymentId, int reties = 0); 153 | 154 | public abstract Task CreatePaymentOnReadyForServerCompletionCallBack(string paymentId, string txid, int retries = 0); 155 | 156 | public abstract Task CreatePaymentOnCancelCallBack(string paymentId); 157 | 158 | public abstract Task CreatePaymentOnErrorCallBack(string paymentId, string txid); 159 | 160 | public virtual async Task OpenShareDialog(string title, string message) 161 | { 162 | await PiNetworkCallJavascript.OpenShareDialog(jsRuntime, title, message); 163 | } 164 | 165 | /// 166 | /// This method is not from Pi network SDK 167 | /// 168 | /// 169 | public virtual async Task IsPiNetworkBrowser() 170 | { 171 | DotNetObjectReference objRef; 172 | 173 | if (this.logger is { }) 174 | objRef = DotNetObjectReference.Create(new PiNetworkMain(this.navigationManager, this, this.sessionStorage, this.loggerFactory)); 175 | else 176 | objRef = DotNetObjectReference.Create(new PiNetworkMain(navigationManager, this, sessionStorage)); 177 | 178 | await PiNetworkCallJavascript.IsPiNetworkBrowser(jsRuntime, objRef); 179 | } 180 | 181 | protected static bool CreatePaymentExceptionMessageValidation(Exception e, string text) => 182 | (!string.IsNullOrEmpty(e.Message) && e.Message.Contains(text)) || 183 | (e.InnerException is { } && !string.IsNullOrEmpty(e.InnerException.Message) && e.Message.Contains(text)) || 184 | (!string.IsNullOrEmpty(e.StackTrace) && e.StackTrace.Contains(text)); 185 | 186 | protected async Task CreatePaymentExceptionProccess(Exception e, string text) 187 | { 188 | if (this.logger is { } && CreatePaymentExceptionMessageValidation(e, text)) 189 | this.logger.LogWarning(e, "Method: {@Method}. Message: {Message}", nameof(CreatePayment), e.Message); 190 | else if (this.logger is { }) 191 | this.logger.LogError(e, "Method: {@Method}. Message: {Message}", nameof(CreatePayment), e.Message); 192 | 193 | await this.sessionStorage.SetItemAsync(Common.PiNetworkConstants.PiNetworkSdkCallBackError, Common.Messages.PaymentError); 194 | 195 | this.navigationManager.NavigateTo($"/", forceLoad: false); 196 | } 197 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/PiNetworkCommon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PiNetwork.Blazor.Sdk.Common; 4 | 5 | public enum Messages 6 | { 7 | AuthenticationError = 0, 8 | PaymentError = 1, 9 | AuthenticationSuccess = 2, 10 | PaymentSuccess = 3 11 | } 12 | 13 | public sealed class PiNetworkConstants 14 | { 15 | public const string PiNetworkSdkCallBackError = "PiNetworkSdkCallBackError"; 16 | public const string PiNetworkDoNotRedirect = "PiNetworkDoNotRedirect"; 17 | public const string IsPiNetworkBrowser = "IsPiNetworkBrowser"; 18 | public const string PiNetworkClient = "PiNetworkClient"; 19 | public const string PiNetworkClientFallback = "PiNetworkClientFallback"; 20 | public const string PiTestnet = "Pi Testnet"; 21 | public const string PiNetwork = "Pi Network"; 22 | } 23 | 24 | public sealed class PiNetworkException : Exception 25 | { 26 | public PiNetworkException(string message) : base(message) 27 | 28 | { 29 | } 30 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/PiNetworkU2AServerBlazor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.Logging; 3 | using PiNetwork.Blazor.Sdk.Common; 4 | using PiNetwork.Blazor.Sdk.Dto.Payment; 5 | using System; 6 | using System.Net.Http; 7 | using System.Net.Http.Json; 8 | using System.Threading.Tasks; 9 | 10 | namespace PiNetwork.Blazor.Sdk; 11 | 12 | public interface IPiNetworkU2AServerBlazor 13 | { 14 | Task PaymentGet(string paymentId); 15 | Task PaymentApprove(string paymentId); 16 | Task PaymentComplete(string paymentId, string txid); 17 | Task PaymentCancel(string paymentId); 18 | } 19 | 20 | public sealed class PiNetworkU2AServerBlazor : IPiNetworkU2AServerBlazor 21 | { 22 | private readonly ILogger logger; 23 | private readonly IConfiguration configuration; 24 | private readonly HttpClient httpClient; 25 | 26 | public PiNetworkU2AServerBlazor(ILoggerFactory loggerFactory, IConfiguration configuration, IHttpClientFactory clientFactory) 27 | { 28 | this.logger = loggerFactory.CreateLogger(this.GetType().Name); 29 | this.configuration = configuration; 30 | this.httpClient = clientFactory.CreateClient(PiNetworkConstants.PiNetworkClient); 31 | } 32 | 33 | public async Task PaymentGet(string paymentId) 34 | { 35 | try 36 | { 37 | this.logger.LogInformation("Method: {@Method}. Id: {@Id}", nameof(PaymentGet), paymentId); 38 | 39 | var result = await this.httpClient.GetFromJsonAsync($"payments/{paymentId}"); 40 | return result; 41 | } 42 | catch (Exception e) 43 | { 44 | this.logger.LogError(e, "Method: {@Method}. Id: {@Id}", nameof(PaymentGet), paymentId); 45 | throw; 46 | } 47 | } 48 | 49 | public async Task PaymentApprove(string paymentId) 50 | { 51 | try 52 | { 53 | this.logger.LogInformation("Method: {@Method}. Id: {@Id}", nameof(PaymentApprove), paymentId); 54 | 55 | var httpResponse = await this.httpClient.PostAsync($"payments/{paymentId}/approve", new StringContent("application/json")); 56 | if (httpResponse.IsSuccessStatusCode) 57 | { 58 | var result = await httpResponse.Content.ReadFromJsonAsync(); 59 | return result; 60 | } 61 | else 62 | throw new HttpRequestException($"Method: {nameof(PaymentApprove)}. Http status code is not success: {httpResponse.StatusCode}"); 63 | } 64 | catch (Exception e) 65 | { 66 | this.logger.LogError(e, "Method: {@Method}. Id: {@Id}", nameof(PaymentApprove), paymentId); 67 | throw; 68 | } 69 | } 70 | 71 | public async Task PaymentComplete(string paymentId, string txid) 72 | { 73 | try 74 | { 75 | this.logger.LogInformation("Method: {@Method}. Id: {@Id}", nameof(PaymentComplete), paymentId); 76 | 77 | var httpResponse = await this.httpClient.PostAsJsonAsync($"payments/{paymentId}/complete", new PaymentCompleteDto { Txid = txid }); 78 | if (httpResponse.IsSuccessStatusCode) 79 | { 80 | var result = await httpResponse.Content.ReadFromJsonAsync(); 81 | return result; 82 | } 83 | else 84 | throw new HttpRequestException($"Method: {nameof(PaymentComplete)}. Http status code is not success: {httpResponse.StatusCode}"); 85 | } 86 | catch (Exception e) 87 | { 88 | this.logger.LogError(e, "Method: {@Method}. Id: {@Id}", nameof(PaymentComplete), paymentId); 89 | throw; 90 | } 91 | } 92 | 93 | public async Task PaymentCancel(string paymentId) 94 | { 95 | try 96 | { 97 | this.logger.LogInformation("Method: {@Method}. Id: {@Id}", nameof(PaymentCancel), paymentId); 98 | 99 | var httpResponse = await this.httpClient.PostAsync($"payments/{paymentId}/cancel", new StringContent(String.Empty)); 100 | if (httpResponse.IsSuccessStatusCode) 101 | { 102 | var result = await httpResponse.Content.ReadFromJsonAsync(); 103 | return result; 104 | } 105 | else 106 | throw new HttpRequestException($"Method: {nameof(PaymentCancel)}. Http status code is not success: {httpResponse.StatusCode}"); 107 | } 108 | catch (Exception e) 109 | { 110 | this.logger.LogError(e, "Method: {@Method}. Id: {@Id}", nameof(PaymentCancel), paymentId); 111 | throw; 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using PiNetwork.Blazor.Sdk.Common; 3 | using Polly; 4 | using System; 5 | using System.Net.Http.Headers; 6 | 7 | namespace PiNetwork.Blazor.Sdk; 8 | 9 | public sealed class PiNetworkOptions 10 | { 11 | /// 12 | /// PiNetwork your application Apykey, must be provided 13 | /// 14 | public string ApiKey { get; set; } 15 | 16 | /// 17 | /// PiNetwrok BaseUrl for requests, must be provided 18 | /// 19 | public string BaseUrl { get; set; } 20 | 21 | /// 22 | /// Not yet supported. PiNetwrok BaseUrl fallback in case BaseUrl fails to response this url will be tried, it's optional 23 | /// 24 | public string BaseUrlFallback { get; set; } 25 | } 26 | 27 | public static class ServiceCollectionExtensions 28 | { 29 | public static IServiceCollection AddPiNetwork(this IServiceCollection services, Action configure) 30 | { 31 | try 32 | { 33 | if (configure is null) 34 | throw new PiNetworkException("PiNetwork configuration can't be null"); 35 | 36 | PiNetworkOptions item = new(); 37 | configure.Invoke(item); 38 | 39 | if (string.IsNullOrEmpty(item.ApiKey)) 40 | throw new PiNetworkException("PiNetwork configuration ApiKey can't be null"); 41 | 42 | if (string.IsNullOrEmpty(item.BaseUrl)) 43 | throw new PiNetworkException("PiNetwork configuration BaseUrl can't be null"); 44 | 45 | string url = GetUrlFull(item.BaseUrl); 46 | 47 | services.AddHttpClient(PiNetworkConstants.PiNetworkClient, client => 48 | { 49 | client.BaseAddress = new Uri(url); 50 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Key", item.ApiKey); 51 | }).AddTransientHttpErrorPolicy(s => s.WaitAndRetryAsync(3, times => TimeSpan.FromSeconds(times * 1))); 52 | 53 | //this linko not supported yet 54 | if (!string.IsNullOrEmpty(item.BaseUrlFallback)) 55 | { 56 | url = GetUrlFull(item.BaseUrlFallback); 57 | 58 | services.AddHttpClient(PiNetworkConstants.PiNetworkClientFallback, client => 59 | { 60 | client.BaseAddress = new Uri(url); 61 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Key", item.ApiKey); 62 | }).AddTransientHttpErrorPolicy(s => s.WaitAndRetryAsync(3, times => TimeSpan.FromMilliseconds(times * 300))); 63 | } 64 | 65 | return services; 66 | } 67 | catch (PiNetworkException) 68 | { 69 | throw; 70 | } 71 | catch (Exception) 72 | { 73 | throw; 74 | } 75 | } 76 | 77 | public static string GetUrlFull(string urlOriginal) 78 | { 79 | ReadOnlySpan url = urlOriginal; 80 | 81 | if (url.Slice(url.Length - 1) == "/") 82 | return urlOriginal; 83 | 84 | return $"{urlOriginal}/"; 85 | } 86 | } -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/bundleconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputFileName": "wwwroot/PiNetwork.Blazor.Sdk.min.js", 4 | "inputFiles": [ 5 | "wwwroot/PiNetwork.Blazor.Sdk.js" 6 | ] 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/wwwroot/PiNetwork.Blazor.Sdk.js: -------------------------------------------------------------------------------- 1 | const scopes = ['payments', 'username', 'wallet_address']; 2 | let myDotNetHelper; 3 | 4 | function onIncompletePaymentFound(payment) { 5 | //alert("PiNetwork.Blazor.Sdk.js: onIncompletePaymentFound trigered"); 6 | //alert('Id: ' + payment.identifier + ' TxId: ' + payment.transaction.txid); 7 | myDotNetHelper.invokeMethodAsync('CreatePaymentOnIncopletePaymentFound', payment.identifier, payment.transaction.txid); 8 | } 9 | 10 | (function () { 11 | window.PiNetworkBlazorSdk = { 12 | Authenticate: function (dotNetHelper, redirectUri, retries) { 13 | myDotNetHelper = dotNetHelper; 14 | Pi.authenticate(scopes, onIncompletePaymentFound).then(function (auth) { 15 | //alert(JSON.stringify(auth)); 16 | dotNetHelper.invokeMethodAsync('AuthenticateOnSuccess', JSON.stringify(auth), redirectUri); 17 | }).catch(function (error) { 18 | //alert(JSON.stringify(error)); 19 | dotNetHelper.invokeMethodAsync('AuthenticateOnError', JSON.stringify(error, redirectUri, retries)); 20 | }); 21 | }, 22 | CreatePayment: function (dotNetHelper, amountMe, memoMe, orderIdMe) { 23 | //alert('createPayment'); 24 | myDotNetHelper = dotNetHelper; 25 | Pi.createPayment({ 26 | amount: amountMe, 27 | memo: memoMe, // e.g: "Digital kitten #1234", 28 | metadata: { orderId: orderIdMe }, // e.g: { kittenId: 1234 } 29 | }, { 30 | onReadyForServerApproval: function (paymentId) { 31 | //alert('onReadyForServerApproval paymentId ' + paymentId); 32 | dotNetHelper.invokeMethodAsync('CreatePaymentOnReadyForServerApproval', paymentId); 33 | }, 34 | onReadyForServerCompletion: function (paymentId, txid) { 35 | //alert('onReadyForServerCompletion paymentId ' + paymentId + ' txid ' + txid); 36 | dotNetHelper.invokeMethodAsync('CreatePaymentOnReadyForServerCompletion', paymentId, txid); 37 | }, 38 | onCancel: function (paymentId) { 39 | //alert('onCancel. paymentId ' + paymentId); 40 | dotNetHelper.invokeMethodAsync('CreatePaymentOnCancel', paymentId); 41 | }, 42 | onError: function (error, payment) { 43 | //alert('onError. error:' + JSON.stringify(error) + ' payment id:' + payment.identifier + ' TxId:' + payment.transaction.txid); 44 | if (typeof payment === 'undefined' || payment === null || payment.length === 0) { 45 | dotNetHelper.invokeMethodAsync('CreatePaymentOnError', null, null); 46 | } 47 | else { 48 | //alert('onError. payment id:' + payment.identifier + ' TxId:' + payment.transaction.txid); 49 | dotNetHelper.invokeMethodAsync('CreatePaymentOnError', payment.identifier, payment.transaction.txid); 50 | } 51 | }, 52 | }); 53 | }, 54 | OpenShareDialog: function (title, message) { 55 | Pi.openShareDialog(title, message); 56 | } 57 | }; 58 | window.Browser = { 59 | IsPiNetworkBrowser: function (dotNetHelper) { 60 | if (navigator.userAgent.toLowerCase().includes("pibrowser")) { 61 | dotNetHelper.invokeMethodAsync('IsPiNetworkBrowser'); 62 | } 63 | } 64 | }; 65 | })(); -------------------------------------------------------------------------------- /PiNetwork.Blazor.Sdk/wwwroot/PiNetwork.Blazor.Sdk.min.js: -------------------------------------------------------------------------------- 1 | function onIncompletePaymentFound(n){myDotNetHelper.invokeMethodAsync("CreatePaymentOnIncopletePaymentFound",n.identifier,n.transaction.txid)}const scopes=["payments","username","wallet_address"];let myDotNetHelper;(function(){window.PiNetworkBlazorSdk={Authenticate:function(n,t,i){myDotNetHelper=n;Pi.authenticate(scopes,onIncompletePaymentFound).then(function(i){n.invokeMethodAsync("AuthenticateOnSuccess",JSON.stringify(i),t)}).catch(function(r){n.invokeMethodAsync("AuthenticateOnError",JSON.stringify(r,t,i))})},CreatePayment:function(n,t,i,r){myDotNetHelper=n;Pi.createPayment({amount:t,memo:i,metadata:{orderId:r}},{onReadyForServerApproval:function(t){n.invokeMethodAsync("CreatePaymentOnReadyForServerApproval",t)},onReadyForServerCompletion:function(t,i){n.invokeMethodAsync("CreatePaymentOnReadyForServerCompletion",t,i)},onCancel:function(t){n.invokeMethodAsync("CreatePaymentOnCancel",t)},onError:function(t,i){typeof i=="undefined"||i===null||i.length===0?n.invokeMethodAsync("CreatePaymentOnError",null,null):n.invokeMethodAsync("CreatePaymentOnError",i.identifier,i.transaction.txid)}})},OpenShareDialog:function(n,t){Pi.openShareDialog(n,t)}};window.Browser={IsPiNetworkBrowser:function(n){navigator.userAgent.toLowerCase().includes("pibrowser")&&n.invokeMethodAsync("IsPiNetworkBrowser")}}})(); -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## LICENSE 2 | See LICENSE.md file for more details 3 | 4 | ## INSTALATION 5 | 6 | Clone project, add to your solution. 7 | This setup is for server side Blazor, but for WASM blazor (client side) would be good as well with some small changes. 8 | 9 | Add line to your Blazor project `_Host.cshtml` 10 | 11 | ```html 12 | 13 | ``` 14 | Add lines to your Blazor project `appsettings.json` 15 | 16 | ```json 17 | "PiNetwork": { 18 | "ApiKey": "YourApiKeyForU2Apayments", 19 | "BaseUrl": "https://api.minepi.com/v2", 20 | "DeveloperAccount": "YourDeveloperAccountForA2UPayments" 21 | "DeveloperSeed": "YouDeveloperSeedForA2Upayments", 22 | } 23 | ``` 24 | 25 | Add reference to your Blazor project to this PiNetwork.Blazor.Sdk 26 | 27 | Modify your Blazor project `Startup.cs` file: 28 | ```csharp 29 | public void ConfigureServices(IServiceCollection services) 30 | { 31 | //...your rest code 32 | services.AddScoped(); 33 | services.AddScoped(); //for user to app payments 34 | services.AddScoped(); //for app to user payments 35 | //...your rest code 36 | 37 | services.AddPiNetwork(options => 38 | { 39 | options.ApyKey = this.Configuration["PiNetwork:ApiKey"]; 40 | options.BaseUrl = this.Configuration["PiNetwork:BaseUrl"]; 41 | }); 42 | 43 | //Add this two lines PiNetwork.Blazor.Sdk uses SessionStorage and MemoryCache 44 | services.AddBlazoredSessionStorage(); 45 | 46 | // for server side blazor to protect agains multiple callbacks 47 | // in your abstract class PiNetworkClientBlazor implementation (read bellow) 48 | // for wasm use session storage instead 49 | services.AddMemoryCache(); 50 | 51 | //...your rest code 52 | 53 | } 54 | 55 | // for Pi network browser to work fine, these lines should be added 56 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) 57 | { 58 | 59 | //... your rest code 60 | 61 | app.UseRouting(); 62 | app.UseCookiePolicy( 63 | new CookiePolicyOptions 64 | { 65 | MinimumSameSitePolicy = SameSiteMode.None, 66 | HttpOnly = HttpOnlyPolicy.Always, 67 | Secure = CookieSecurePolicy.Always 68 | } 69 | ); 70 | app.UseAuthentication(); 71 | app.UseAuthorization(); 72 | 73 | //... your rest code 74 | } 75 | ``` 76 | 77 | Now you will need to create class to deal with PiNetwPiNetwork.Blazor.Sdk callbacks. This class must be extended from abstract class PiNetworkClientBlazor. You will need to override some methods to your needs. In example bellow your bussiness logic handler is IOrderServices. 78 | 79 | Put this new class to you Blazor project. Lets call this new class ServicesPiNetworkFacade 80 | ```csharp 81 | public class ServicesPiNetworkU2AFacade : PiNetworkClientBlazor 82 | { 83 | private readonly ILogger logger; 84 | private readonly IPiNetworkServerBlazor server; 85 | private readonly IOrderServices orderServices; 86 | 87 | //If you don't need logging use constructor without ILoggerFactory 88 | public ServicesPiNetworkU2AFacade(ILoggerFactory loggerFactory, 89 | NavigationManager navigationManager, 90 | IJSRuntime jsRuntime, 91 | ISessionStorageService sessionStorage, 92 | IPiNetworkU2AServerBlazor server, 93 | IOrderServices orderServices) 94 | : base(navigationManager, jsRuntime, sessionStorage, loggerFactory) 95 | { 96 | this.logger = loggerFactory.CreateLogger(this.GetType().Name); 97 | this.server = server; 98 | this.orderServices = orderServices; //your bussiness logic is here. 99 | } 100 | 101 | public override async Task CreatePaymentOnReadyForServerApprovalCallBack(string paymentId) 102 | { 103 | //...you code 104 | var result = await this.server.PaymentApprove(paymentId); //don't change this row. 105 | //...app bussiness logic 106 | await this.orderServices.SavePaymentStatusPiNetwork(result.Metadata.OrderId, Enums.PaymentStatus.Waiting, null); 107 | } 108 | 109 | public override async Task CreatePaymentOnReadyForServerCompletionCallBack(string paymentId, string txid) 110 | { 111 | //..you code 112 | var result = await this.server.PaymentComplete(paymentId, txid); //don't change this row 113 | //...app bussiness logic 114 | await this.orderServices.SavePaymentStatusPiNetwork(result.Metadata.OrderId, Enums.PaymentStatus.AdditionalSuccess, txid); 115 | this.navigationManager.NavigateTo($"myorders/{result.Metadata.OrderId}", forceLoad: true); 116 | } 117 | 118 | public override Task AuthenticateOnErrorCallBack(object error) 119 | { 120 | } 121 | 122 | public override Task CreatePaymentOnCancelCallBack(string paymentId) 123 | { 124 | } 125 | 126 | public override Task CreatePaymentOnErrorCallBack(object error, PaymentDto payment) 127 | { 128 | } 129 | 130 | public override Task OpenShareDialog(string title, string message) 131 | { 132 | } 133 | } 134 | ``` 135 | 136 | ## SEE REAL WORLD PRODUCTION EXAMPLE AND USE FOR YOUR APP 137 | See ServicesPiNetworkU2AFacade.md 138 | 139 | ## RETRIES 140 | By default if something fails PiNetworkClientBlazor will try to retry (if it is reasonable to do so and could solve problem) up to 10 times with 1000 milliseconds intervals. 141 | You can override these values in ServicesPiNetworkFacade: 142 | ```csharp 143 | public virtual int Retries { get; set; } = 10; 144 | 145 | public virtual int RetryDelay { get; set; } = 1000; 146 | ``` 147 | ## USE ENUMS FOR MESSAGES IN PiNetworkConstantsEnums.cs 148 | See PiNetwork.Blazor.Sdk.ConstantsEnums for messages to pass to front side. Messages stored in SessionStorage. 149 | ```csharp 150 | AuthenticationError = 0, 151 | PaymentError = 1, 152 | AuthenticationSuccess = 2, 153 | PaymentSuccess = 3 154 | ``` 155 | ## USE CONSTANTS IN PiNetworkCommon.cs 156 | ```csharp 157 | public const string PiNetworkSdkCallBackError = "PiNetworkSdkCallBackError"; 158 | public const string PiNetworkDoNotRedirect = "PiNetworkDoNotRedirect"; 159 | public const string IsPiNetworkBrowser = "IsPiNetworkBrowser"; 160 | // rest code 161 | ``` 162 | If you don't need to redirect use constant "PiNetworkDoNotRedirect" this is used in AuthenticateOnSuccessCallBack(AuthResultDto auth, string redirectUri). 163 | See ServicesPiNetworkU2AFacade.md. 164 | 165 | ## OPEN SHARE DIALOG 166 | Inject in your *.razor file '@inject IPiNetworkClientBlazor piClient' 167 | ```csharp 168 | //OpenShareDialog(string title, string message) 169 | await this.piClient.OpenShareDialog("Share", "https://www.your-pi-network-project.com")) 170 | ``` 171 | 172 | ## CHECK IF BROWSER IS PI NETWORK BROWSER 173 | PiNetworkClientBlazor.IsPiNetworkBrowser() this method is not from PI SDK, but still very practical. 174 | 175 | ## HOW TO AUTHENTICATE AND MAKE PAYMENTS FROM YOUR BLAZOR APP? 176 | 177 | ### AUTHENTICATE 178 | Inject in your '*.razor' file '@inject IPiNetworkClientBlazor piClient' 179 | ```csharp 180 | await this.piClient.Authenticate(redirectUri, 0); // 0 - first attempt 181 | ``` 182 | If you don't need to redirect use ConstantsEnums.PiNetworkDoNotRedirect (if you provide just empty string it will redirect to "/"). 183 | 184 | ### MAKE U2A PAYMENTS 185 | Inject inyour *.razor file '@inject IPiNetworkClientBlazor piClient' 186 | 187 | First authenticate with ConstantsEnums.PiNetworkDoNotRedirect option. Authenticate every time before making payment. 188 | ```csharp 189 | await this.piClient.Authenticate(ConstantsEnums.PiNetworkDoNotRedirect, 0); // 0 - first attempt 190 | ``` 191 | Now make payment 192 | ```csharp 193 | //memo - order discription. Don't exceed 25 characters. 194 | //public virtual async Task CreatePayment(decimal amount, string memo, int orderId, int retries = 0) 195 | 196 | await this.piClient.CreatePayment(total, memo, OrderId); 197 | ``` -------------------------------------------------------------------------------- /ServicesPiNetworkU2AFacade.md: -------------------------------------------------------------------------------- 1 | ```csharp 2 | 3 | public sealed class ServicesPiNetworkU2AFacade : PiNetworkClientBlazor 4 | { 5 | private readonly ILogger logger; 6 | private readonly ISessionStorageService sessionStorage; 7 | private readonly IPiNetworkU2AServerBlazor server; 8 | private readonly IOrderService orderServices; 9 | private readonly IUserInfoService userInfoService; 10 | private readonly IMailingService mailingService; 11 | private readonly IMemoryCache cache; 12 | private readonly IPiNetworkAuthStateProvider authenticationState; 13 | 14 | public ServicesPiNetworkU2AFacade(ILoggerFactory loggerFactory, 15 | NavigationManager navigationManager, 16 | IJSRuntime jsRuntime, 17 | ISessionStorageService sessionStorage, 18 | IPiNetworkU2AServerBlazor server, 19 | IOrderService orderServices, 20 | IUserInfoService userInfoService, 21 | IMailingService mailingService, 22 | IMemoryCache cache, 23 | IPiNetworkAuthStateProvider authenticationState) 24 | : base(navigationManager, jsRuntime, sessionStorage, loggerFactory) 25 | { 26 | this.logger = loggerFactory.CreateLogger(this.GetType().Name); 27 | this.sessionStorage = sessionStorage; 28 | this.server = server; 29 | this.orderServices = orderServices; 30 | this.userInfoService = userInfoService; 31 | this.mailingService = mailingService; 32 | this.cache = cache; 33 | this.authenticationState = authenticationState; 34 | } 35 | 36 | public override int Retries { get; set; } = 8; 37 | 38 | public override async Task AuthenticateOnErrorCallBack(string error, string redirectUri, int retries = 0) 39 | { 40 | if (retries <= base.Retries && (string.IsNullOrEmpty(error) || error.Equals("{}"))) 41 | { 42 | this.logger.LogWarning("Method: {@Method}. Error: {@Error}", nameof(AuthenticateOnErrorCallBack), error); 43 | await Authenticate(redirectUri, retries + 1); 44 | } 45 | else 46 | this.logger.LogError("Method: {@Method}. Error: {@Error}", nameof(AuthenticateOnErrorCallBack), error); 47 | 48 | await this.sessionStorage.SetItemAsync(PiNetworkConstants.PiNetworkSdkCallBackError, Messages.AuthenticationError); 49 | 50 | this.navigationManager.NavigateTo($"/", forceLoad: false); 51 | } 52 | 53 | public override async Task AuthenticateOnSuccessCallBack(string authString, string redirectUri) 54 | { 55 | try 56 | { 57 | var auth = JsonSerializer.Deserialize(authString); 58 | if (this.logger is { }) 59 | this.logger.LogInformation("Method: {@Method}. Authentication: {@Authentication} RedirectUri: {RedirectUri}", nameof(AuthenticateOnSuccessCallBack), auth, redirectUri); 60 | 61 | 62 | if (string.IsNullOrEmpty(auth.User.Username)) 63 | { 64 | if (this.logger is { }) 65 | this.logger.LogError("Method: {@Method}. Pi network SDK not returning 'username', it must be returned, to properly authenticate. Authentication: {@Authentication} RedirectUri: {RedirectUri}", nameof(AuthenticateOnSuccessCallBack), auth, redirectUri); 66 | 67 | this.navigationManager.NavigateTo("signin/error"); 68 | } 69 | 70 | if (redirectUri.Equals(PiNetworkConstants.PiNetworkDoNotRedirect)) 71 | return; 72 | 73 | var userExisting = await this.userInfoService.Get(auth.User.Username); 74 | if (userExisting is { }) 75 | await this.authenticationState.LoginAsync(userExisting); 76 | else 77 | { 78 | var userNew = new Domains.View.UserInfoView { PiNetworkId = auth.User.Username }; 79 | await this.userInfoService.AddAsync(userNew); 80 | await this.authenticationState.LoginAsync(userNew); 81 | } 82 | 83 | this.navigationManager.NavigateTo($"{redirectUri}", true); 84 | } 85 | catch (Exception e) 86 | { 87 | if (this.logger is { }) 88 | this.logger.LogErrorMy(e, "Method: {@Method}. Authentication: {@Authentication}. Redirect: {@Redirect}", nameof(AuthenticateOnSuccessCallBack), authString, redirectUri); 89 | throw; 90 | } 91 | } 92 | 93 | public override async Task CreatePaymentOnReadyForServerApprovalCallBack(string paymentId, int retries = 0) 94 | { 95 | try 96 | { 97 | string key = $"CreatePaymentOnReadyForServerApprovalCallBack-{paymentId}"; 98 | 99 | if (!this.cache.TryGetValue(key, out bool exists)) 100 | { 101 | this.cache.Set(key, true, TimeSpan.FromSeconds(60)); 102 | 103 | if (this.logger is { }) 104 | this.logger.LogInformation("Method: {@Method}. PaymentId: {@PaymentId}", nameof(CreatePaymentOnReadyForServerApprovalCallBack), paymentId); 105 | 106 | var result = await this.server.PaymentApprove(paymentId); 107 | 108 | if (this.logger is { }) 109 | this.logger.LogInformation("Method: {@Method}. Result: {@Result}", nameof(CreatePaymentOnReadyForServerApprovalCallBack), result); 110 | 111 | await this.orderServices.SavePaymentStatus(result.Metadata.OrderId, Enums.PaymentStatus.Approved, null, null); 112 | } 113 | } 114 | catch (Exception e) 115 | { 116 | if (e is HttpRequestException) 117 | { 118 | string textToSearch = "NotFound"; 119 | if (CreatePaymentExceptionMessageValidation(e, textToSearch) && retries <= base.Retries) 120 | { 121 | await Task.Delay(base.RetryDelay); 122 | await CreatePaymentOnReadyForServerApprovalCallBack(paymentId, retries + 1); 123 | } 124 | else 125 | await CreatePaymentExceptionProccess(e, textToSearch); 126 | } 127 | 128 | if (this.logger is { }) 129 | this.logger.LogErrorMy(e, "Method: {@Method}. PaymentId: {@paymentId}", nameof(CreatePaymentOnReadyForServerApprovalCallBack), paymentId); 130 | 131 | this.navigationManager.NavigateTo($"/", forceLoad: false); 132 | } 133 | } 134 | 135 | public override async Task CreatePaymentOnReadyForServerCompletionCallBack(string paymentId, string txid, int retries = 0) 136 | { 137 | try 138 | { 139 | string key = $"CreatePaymentOnReadyForServerCompletionCallBack-{paymentId}"; 140 | 141 | if (!this.cache.TryGetValue(key, out bool exists)) 142 | { 143 | this.cache.Set(key, true, TimeSpan.FromSeconds(60)); 144 | 145 | if (this.logger is { }) 146 | this.logger.LogInformation("Method: {@Method}. PaymentId: {@PaymentId}. TxId: {@TxId}", nameof(CreatePaymentOnReadyForServerCompletionCallBack), paymentId, txid); 147 | 148 | var result = await this.server.PaymentComplete(paymentId, txid); 149 | 150 | if (this.logger is { }) 151 | this.logger.LogInformation("Method: {@Method}. Result: {@Result}", nameof(CreatePaymentOnReadyForServerCompletionCallBack), result); 152 | 153 | await this.orderServices.SavePaymentStatus(result.Metadata.OrderId, Enums.PaymentStatus.Confirmed, txid, result.FromAddress); 154 | await OrderStateChange(); 155 | 156 | var orderOriginal = await this.orderServices.GetAsync(result.Metadata.OrderId); 157 | var userOriginal = await this.userInfoService.GetAsync(orderOriginal.UserInfoId); 158 | 159 | await this.mailingService.OnOrderFilledEmailAsync(orderOriginal.Email, orderOriginal, orderOriginal.Id); 160 | 161 | this.navigationManager.NavigateTo($"myorders/{result.Metadata.OrderId}", forceLoad: true); 162 | } 163 | } 164 | catch (Exception e) 165 | { 166 | if (e is HttpRequestException) 167 | { 168 | string textToSearch = "NotFound"; 169 | if (CreatePaymentExceptionMessageValidation(e, textToSearch) && retries <= base.Retries) 170 | { 171 | await Task.Delay(base.RetryDelay); 172 | await CreatePaymentOnReadyForServerCompletionCallBack(paymentId, txid, retries + 1); 173 | } 174 | else 175 | await CreatePaymentExceptionProccess(e, textToSearch); 176 | } 177 | 178 | if (this.logger is { }) 179 | this.logger.LogErrorMy(e, "Method: {@Method}. PaymentId: {@PaymentId} TxId: {@TxId}", nameof(CreatePaymentOnReadyForServerCompletionCallBack), paymentId, txid); 180 | 181 | this.navigationManager.NavigateTo($"/", forceLoad: true); 182 | } 183 | } 184 | 185 | public override async Task CreatePaymentOnCancelCallBack(string paymentId) 186 | { 187 | string key = $"CreatePaymentOnCancelCallBack-{paymentId}"; 188 | 189 | try 190 | { 191 | if (!this.cache.TryGetValue(key, out bool exists)) 192 | { 193 | this.cache.Set(key, true, TimeSpan.FromSeconds(60)); 194 | 195 | if (this.logger is { }) 196 | this.logger.LogInformation("Method: {@Method}. PaymentId: {@PaymentId}", nameof(CreatePaymentOnCancelCallBack), paymentId); 197 | 198 | await server.PaymentCancel(paymentId); 199 | 200 | await OrderStateChange(); 201 | 202 | this.navigationManager.NavigateTo($"/", forceLoad: true); 203 | } 204 | } 205 | catch (Exception e) 206 | { 207 | if (this.logger is { }) 208 | this.logger.LogErrorMy(e, "Method: {@Method}. PaymentId: {@PaymentId}", nameof(CreatePaymentOnCancelCallBack), paymentId); 209 | 210 | this.navigationManager.NavigateTo($"/", forceLoad: true); 211 | } 212 | } 213 | 214 | public override async Task CreatePaymentOnErrorCallBack(string paymentId, string txId) 215 | { 216 | string key = $"CreatePaymentOnErrorCallBack-{paymentId}"; 217 | 218 | try 219 | { 220 | if (!this.cache.TryGetValue(key, out bool exists)) 221 | { 222 | this.cache.Set(key, true, TimeSpan.FromSeconds(60)); 223 | 224 | if (this.logger is { }) 225 | this.logger.LogWarning("Method: {@Method}. Id: {@Id} TxId: {@TxId}", nameof(CreatePaymentOnErrorCallBack), paymentId, txId); 226 | 227 | if (!string.IsNullOrEmpty(paymentId) && !string.IsNullOrEmpty(txId)) 228 | { 229 | await CreatePaymentOnReadyForServerCompletionCallBack(paymentId, txId); 230 | } 231 | else 232 | { 233 | await OrderStateChange(); 234 | } 235 | 236 | await this.sessionStorage.SetItemAsync(PiNetworkConstants.PiNetworkSdkCallBackError, Messages.PaymentError); 237 | 238 | this.navigationManager.NavigateTo($"/", forceLoad: false); 239 | } 240 | } 241 | catch (Exception e) 242 | { 243 | if (this.logger is { }) 244 | this.logger.LogErrorMy(e, "Method: {@Method}. PaymentId: {@PaymentId}", nameof(CreatePaymentOnErrorCallBack), paymentId); 245 | throw; 246 | } 247 | } 248 | 249 | private async Task OrderStateChange() 250 | { 251 | try 252 | { 253 | var orderState = new OrderState(); 254 | await this.sessionStorage.SetItemAsync(Constants.SessionOrderState, orderState); 255 | await this.sessionStorage.SetItemAsync(Constants.SessionBasketCounter, 0); 256 | } 257 | catch (Exception e) 258 | { 259 | if (this.logger is { }) 260 | this.logger.LogErrorMy(e, "Method: {@Method}. ", nameof(OrderStateChange)); 261 | throw; 262 | } 263 | } 264 | } 265 | ``` -------------------------------------------------------------------------------- /src/constants.py: -------------------------------------------------------------------------------- 1 | # src/constants.py 2 | 3 | """ 4 | Pi Coin Configuration Constants 5 | This module contains constants related to the Pi Coin cryptocurrency. 6 | """ 7 | 8 | # Pi Coin Symbol 9 | PI_COIN_SYMBOL = "Pi" # Symbol for Pi Coin 10 | 11 | # Pi Coin Value 12 | PI_COIN_VALUE = 314159 # Fixed value of Pi Coin in USD 13 | 14 | # Pi Coin Supply 15 | PI_COIN_SUPPLY = 100_000_000_000 # Total supply of Pi Coin 16 | 17 | # Pi Coin Transaction Fee 18 | PI_COIN_TRANSACTION_FEE = 0.01 # Transaction fee in USD 19 | 20 | # Pi Coin Block Time 21 | PI_COIN_BLOCK_TIME = 10 # Average block time in seconds 22 | 23 | # Pi Coin Mining Difficulty 24 | PI_COIN_MINING_DIFFICULTY = 1000 # Difficulty level for mining Pi Coin 25 | 26 | # Pi Coin Reward for Mining 27 | PI_COIN_MINING_REWARD = 12.5 # Reward for mining a block 28 | 29 | # Pi Coin Network Protocol 30 | PI_COIN_NETWORK_PROTOCOL = "PoS" # Proof of Stake 31 | 32 | # Pi Coin Maximum Transaction Size 33 | PI_COIN_MAX_TRANSACTION_SIZE = 1_000_000 # Maximum transaction size in bytes 34 | 35 | # Pi Coin Decimals 36 | PI_COIN_DECIMALS = 18 # Number of decimal places for Pi Coin 37 | 38 | # Pi Coin Genesis Block Timestamp 39 | PI_COIN_GENESIS_BLOCK_TIMESTAMP = "2023-01-01T00:00:00Z" # Timestamp of the genesis block 40 | 41 | # Pi Coin Governance Model 42 | PI_COIN_GOVERNANCE_MODEL = "Decentralized" # Governance model for Pi Coin 43 | 44 | # Pi Coin Security Features 45 | PI_COIN_ENCRYPTION_ALGORITHM = "AES-256" # Encryption algorithm for securing transactions 46 | PI_COIN_HASHING_ALGORITHM = "SHA-256" # Hashing algorithm for block verification 47 | PI_COIN_SIGNATURE_SCHEME = "ECDSA" # Digital signature scheme for transaction signing 48 | 49 | # Pi Coin Network Parameters 50 | PI_COIN_MAX_PEERS = 100 # Maximum number of peers in the network 51 | PI_COIN_NODE_TIMEOUT = 30 # Timeout for node responses in seconds 52 | PI_COIN_CONNECTION_RETRY_INTERVAL = 5 # Retry interval for node connections in seconds 53 | 54 | # Pi Coin Staking Parameters 55 | PI_COIN_MIN_STAKE_AMOUNT = 100 # Minimum amount required to stake 56 | PI_COIN_STAKE_REWARD_RATE = 0.05 # Annual reward rate for staking 57 | 58 | # Pi Coin API Rate Limits 59 | PI_COIN_API_REQUEST_LIMIT = 1000 # Maximum API requests per hour 60 | PI_COIN_API_KEY_EXPIRATION = 3600 # API key expiration time in seconds 61 | 62 | # Pi Coin Regulatory Compliance 63 | PI_COIN_KYC_REQUIRED = True # Whether KYC is required for transactions 64 | PI_COIN_COMPLIANCE_JURISDICTIONS = ["US", "EU", "UK"] # Jurisdictions for compliance 65 | 66 | # Additional constants can be added here as needed 67 | --------------------------------------------------------------------------------