[
    {
        "id": "cc6b5faa72d5d2a2",
        "type": "group",
        "z": "8fa0a8b0cc9a1698",
        "name": "OAuth2 Flow: User Auth (OAuth Web App) against Adobe",
        "style": {
            "label": true
        },
        "nodes": [
            "f6bf7d175d620ed0",
            "b93723e2d0bcd9d2",
            "dfe639609c714ed0",
            "60bfaf124b249e4c",
            "7f02ccc1c2bfea48",
            "8704aeec6f78280e"
        ],
        "x": 48,
        "y": 879,
        "w": 1264,
        "h": 522
    },
    {
        "id": "f6bf7d175d620ed0",
        "type": "comment",
        "z": "8fa0a8b0cc9a1698",
        "g": "cc6b5faa72d5d2a2",
        "name": "!! Configuration required !!",
        "info": "Authentication against Adobe requires setting up a project in the Adobe Developer Console.\n\nCheck our 'Getting Started' documentation for details: \n[Getting Started with Frame.io](https://docs.qibb.com/platform/frame-io)",
        "x": 250,
        "y": 920,
        "wires": []
    },
    {
        "id": "b93723e2d0bcd9d2",
        "type": "link out",
        "z": "8fa0a8b0cc9a1698",
        "g": "cc6b5faa72d5d2a2",
        "name": "link out 9",
        "mode": "link",
        "links": [],
        "x": 555,
        "y": 1320,
        "wires": []
    },
    {
        "id": "dfe639609c714ed0",
        "type": "group",
        "z": "8fa0a8b0cc9a1698",
        "g": "cc6b5faa72d5d2a2",
        "name": "get token",
        "style": {
            "label": true
        },
        "nodes": [
            "8f73341a456a3e56",
            "a8d1c61a0d69ee7e",
            "d18fdd119e335a82",
            "7c36bba1f29d0567",
            "10c86326b250c030"
        ],
        "x": 74,
        "y": 999,
        "w": 552,
        "h": 142
    },
    {
        "id": "8f73341a456a3e56",
        "type": "oauth2",
        "z": "8fa0a8b0cc9a1698",
        "g": "dfe639609c714ed0",
        "name": "new token",
        "container": "oauth2Response",
        "grant_type": "authorization_code",
        "access_token_url": "https://ims-na1.adobelogin.com/ims/token/v3",
        "authorization_endpoint": "https://ims-na1.adobelogin.com/ims/authorize/v2",
        "redirect_uri": "https://flow-xyz.xyz.qibb.com/oauth2/redirect",
        "open_authentication": "",
        "username": "",
        "password": "",
        "client_id": "",
        "client_secret": "",
        "response_type": "",
        "access_type": "",
        "refresh_token": "",
        "prompt": "",
        "scope": "offline_access,profile,email,openid,additional_info.roles,AdobeID",
        "resource": "",
        "state": "somestate",
        "proxy": "",
        "debug": false,
        "force": false,
        "senderr": false,
        "client_credentials_in_body": false,
        "rejectUnauthorized": false,
        "headers": {},
        "x": 250,
        "y": 1100,
        "wires": [
            [
                "a8d1c61a0d69ee7e"
            ]
        ]
    },
    {
        "id": "a8d1c61a0d69ee7e",
        "type": "function",
        "z": "8fa0a8b0cc9a1698",
        "g": "dfe639609c714ed0",
        "name": "save token",
        "func": "if (msg.oauth2Response.refresh_token) {\n    const existing = flow.get('frameIo') || {};\n    flow.set('frameIo', {...existing, refreshToken: msg.oauth2Response.refresh_token});\n}\nif (msg.oauth2Response.access_token) {\n    const existing = flow.get('frameIo') || {};\n    flow.set('frameIo', {...existing, accessToken: msg.oauth2Response.access_token});\n}\n\n// include expiry time\nconst expirationSeconds = msg.oauth2Response.expires_in;\nconst currentTime = Math.floor(Date.now() / 1000);\n\nif (expirationSeconds) {\n    const expirationDate = currentTime + expirationSeconds;\n    const existing = flow.get('frameIo') || {};\n    flow.set('frameIo', {...existing, expirationDate});\n}\n\nif (msg.oauth2Response.refresh_token && msg.oauth2Response.access_token) {\n    node.status({fill:\"green\", shape:\"dot\", text:\"Tokens saved\"});\n} else {\n    node.status({fill:\"red\", shape:\"dot\", text:\"Failed to save tokens\"});\n}\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 410,
        "y": 1100,
        "wires": [
            [
                "10c86326b250c030"
            ]
        ]
    },
    {
        "id": "d18fdd119e335a82",
        "type": "inject",
        "z": "8fa0a8b0cc9a1698",
        "g": "dfe639609c714ed0",
        "name": "",
        "props": [],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 135,
        "y": 1100,
        "wires": [
            [
                "8f73341a456a3e56"
            ]
        ],
        "l": false
    },
    {
        "id": "7c36bba1f29d0567",
        "type": "comment",
        "z": "8fa0a8b0cc9a1698",
        "g": "dfe639609c714ed0",
        "name": "Start by triggering oauth2 process in oauth2 node, then trigger inject node",
        "info": "Check our 'Getting Started' documentation for details: \n[Getting Started with Frame.io](https://docs.qibb.com/platform/frame-io)\n\nThis will populate the read-only field 'code' in the node configs.\n\nWhen the tokens are invalid, this code must be renewed.",
        "x": 350,
        "y": 1040,
        "wires": []
    },
    {
        "id": "10c86326b250c030",
        "type": "link out",
        "z": "8fa0a8b0cc9a1698",
        "g": "dfe639609c714ed0",
        "name": "link out 10",
        "mode": "link",
        "links": [],
        "x": 545,
        "y": 1100,
        "wires": []
    },
    {
        "id": "60bfaf124b249e4c",
        "type": "group",
        "z": "8fa0a8b0cc9a1698",
        "g": "cc6b5faa72d5d2a2",
        "name": "refresh_token flow",
        "style": {
            "label": true
        },
        "nodes": [
            "b18bb45fd53894b6",
            "a13aeb34fa721552",
            "16a9c9b61c4f0b65",
            "dbcf981176fccb0b",
            "1e4fb1a92286fc74"
        ],
        "x": 514,
        "y": 1219,
        "w": 772,
        "h": 142
    },
    {
        "id": "b18bb45fd53894b6",
        "type": "function",
        "z": "8fa0a8b0cc9a1698",
        "g": "60bfaf124b249e4c",
        "name": "config refresh flow",
        "func": "let refreshToken = flow.get(\"frameIo.refreshToken\")\n\nif (!refreshToken) {\n    node.error(\"No refresh token available in flow context\", msg);\n    node.status({fill:\"red\", shape:\"dot\", text:\"Refresh token not found\"});\n    return null;\n}\n\nmsg.oauth2Request = {\n    access_token_url: \"https://ims-na1.adobelogin.com/ims/token/v3\",\n    credentials: {\n        grant_type: \"refresh_token\",\n        client_id: global.get(\"SECRETS.frameIoTestConfig.client_id\"),\n        client_secret: global.get(\"SECRETS.frameIoTestConfig.client_secret\"),\n        scope: \"offline_access profile email openid additional_info.roles AdobeID\",\n        refresh_token: refreshToken\n    }\n};\n\nnode.status({fill:\"green\", shape:\"dot\", text:\"Request configured\"});\n\nmsg.useRefreshToken = true;\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 630,
        "y": 1260,
        "wires": [
            [
                "a13aeb34fa721552"
            ]
        ]
    },
    {
        "id": "a13aeb34fa721552",
        "type": "oauth2",
        "z": "8fa0a8b0cc9a1698",
        "g": "60bfaf124b249e4c",
        "name": "refresh_token",
        "container": "oauth2Response",
        "grant_type": "refresh_token",
        "access_token_url": "",
        "authorization_endpoint": "",
        "redirect_uri": "https://flow-xyz.xyz.qibb.com/oauth2/redirect",
        "open_authentication": "",
        "username": "",
        "password": "",
        "client_id": "",
        "client_secret": "",
        "response_type": "",
        "access_type": "",
        "refresh_token": "",
        "prompt": "",
        "scope": "",
        "resource": "",
        "state": "",
        "proxy": "",
        "debug": true,
        "force": true,
        "senderr": false,
        "client_credentials_in_body": true,
        "rejectUnauthorized": true,
        "headers": {},
        "x": 850,
        "y": 1260,
        "wires": [
            [
                "16a9c9b61c4f0b65"
            ]
        ]
    },
    {
        "id": "16a9c9b61c4f0b65",
        "type": "function",
        "z": "8fa0a8b0cc9a1698",
        "g": "60bfaf124b249e4c",
        "name": "save token",
        "func": "if (msg?.oauth2Response?.refresh_token) {\n    const existing = flow.get('frameIo') || {};\n    flow.set('frameIo', {... existing, refreshToken: msg.oauth2Response.refresh_token});\n}\nif (msg?.oauth2Response?.access_token) {\n    const existing = flow.get('frameIo') || {};\n    flow.set('frameIo', {... existing,  accessToken: msg.oauth2Response.access_token });\n}\n\n// include expiry time\nconst expirationSeconds = msg?.oauth2Response?.expires_in;\nconst currentTime = Math.floor(Date.now() / 1000);\n\nlet expirationDate = flow.get('frameIo')?.expirationDate;\nif (expirationSeconds) {\n    // Always overwrite expirationDate with new value\n    const expirationDate = currentTime + expirationSeconds;\n    const existing = flow.get('frameIo') || {};\n    flow.set('frameIo', {...existing, expirationDate});\n}\n\nif (msg?.oauth2Response?.refresh_token && msg?.oauth2Response?.access_token) {\n    node.status({fill:\"green\", shape:\"dot\", text:\"Tokens saved\"});\n} else {\n    node.status({ fill: \"red\", shape: \"dot\", text: \"Refresh token not valid. Manual steps required.\" });\n}\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1060,
        "y": 1260,
        "wires": [
            [
                "1e4fb1a92286fc74"
            ]
        ]
    },
    {
        "id": "dbcf981176fccb0b",
        "type": "comment",
        "z": "8fa0a8b0cc9a1698",
        "g": "60bfaf124b249e4c",
        "name": "!! If refresh request fails, perform manual steps in the 'get token' group. ",
        "info": "Authentication against Adobe requires setting up a project in the Adobe Developer Console.\n\nCheck our 'Getting Started' documentation for details: \n[Getting Started with Frame.io](https://docs.qibb.com/platform/frame-io)",
        "x": 1020,
        "y": 1320,
        "wires": []
    },
    {
        "id": "1e4fb1a92286fc74",
        "type": "link out",
        "z": "8fa0a8b0cc9a1698",
        "g": "60bfaf124b249e4c",
        "name": "link out 11",
        "mode": "link",
        "links": [
            "8704aeec6f78280e"
        ],
        "x": 1205,
        "y": 1260,
        "wires": []
    },
    {
        "id": "7f02ccc1c2bfea48",
        "type": "group",
        "z": "8fa0a8b0cc9a1698",
        "g": "cc6b5faa72d5d2a2",
        "name": "use token",
        "style": {
            "label": true
        },
        "nodes": [
            "6b7b30e3f304371a",
            "f206095c18172f76"
        ],
        "x": 74,
        "y": 1199,
        "w": 392,
        "h": 122
    },
    {
        "id": "6b7b30e3f304371a",
        "type": "function",
        "z": "8fa0a8b0cc9a1698",
        "g": "7f02ccc1c2bfea48",
        "name": "check for existing valid token",
        "func": "const currentTime = Math.floor(Date.now() / 1000);\nconst frameIo = flow.get(\"frameIo\");\nconst refreshToken = frameIo?.refreshToken || null;\nconst expirationDate = frameIo?.expirationDate || null;\n\n// // Define threshold for early refresh: 30 minutes (1800 seconds)\nconst refreshThreshold = 1800;\n\nlet isExpiredOrNearExpiry = true;\nif (expirationDate) {\n    // Check if token is expired or will expire within 30 minutes\n    isExpiredOrNearExpiry = (expirationDate - currentTime) <= refreshThreshold;\n}\n\nif (isExpiredOrNearExpiry) {\n    if (refreshToken) {\n        node.status({ fill: \"yellow\", shape: \"dot\", text: \"Token expired or near expiry, refresh token available\" });\n        return [msg, null];\n    } else {\n        node.status({ fill: \"red\", shape: \"ring\", text: \"Token expired or near expiry, no refresh token. Manual steps required\" });\n        return [null, null]; \n    }\n} else {\n    node.status({ fill: \"green\", shape: \"dot\", text: \"Token valid\" });\n    return [null, msg];\n}",
        "outputs": 2,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 320,
        "y": 1280,
        "wires": [
            [
                "b18bb45fd53894b6"
            ],
            [
                "b93723e2d0bcd9d2"
            ]
        ],
        "outputLabels": [
            "refresh flow",
            "valid"
        ]
    },
    {
        "id": "f206095c18172f76",
        "type": "inject",
        "z": "8fa0a8b0cc9a1698",
        "g": "7f02ccc1c2bfea48",
        "name": "refresh every 30 minutes",
        "props": [],
        "repeat": "1860",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 230,
        "y": 1240,
        "wires": [
            [
                "6b7b30e3f304371a"
            ]
        ]
    },
    {
        "id": "8704aeec6f78280e",
        "type": "link in",
        "z": "8fa0a8b0cc9a1698",
        "g": "cc6b5faa72d5d2a2",
        "name": "link in 5",
        "links": [
            "1e4fb1a92286fc74"
        ],
        "x": 165,
        "y": 1360,
        "wires": [
            [
                "6b7b30e3f304371a"
            ]
        ]
    }
]