diff --git a/examples/fetch_ic/src/backend/server.ts b/examples/fetch_ic/src/backend/server.ts index b1d947e364..2be3024f83 100644 --- a/examples/fetch_ic/src/backend/server.ts +++ b/examples/fetch_ic/src/backend/server.ts @@ -7,9 +7,9 @@ export default Server(() => { app.use(express.json()); app.get('/headers-array', (req, res) => { - res.setHeader('x-azle-response-0', 'x-azle-response-0'); - res.setHeader('x-azle-response-1', 'x-azle-response-1'); - res.setHeader('x-azle-response-2', 'x-azle-response-2'); + res.setHeader('X-Azle-Response-Key-0', 'X-Azle-Response-Value-0'); + res.setHeader('X-Azle-Response-Key-1', 'X-Azle-Response-Value-1'); + res.setHeader('X-Azle-Response-Key-2', 'X-Azle-Response-Value-2'); res.json({ whoami: ic.caller().toString(), @@ -18,9 +18,9 @@ export default Server(() => { }); app.get('/headers-object', (req, res) => { - res.setHeader('x-azle-response-0', 'x-azle-response-0'); - res.setHeader('x-azle-response-1', 'x-azle-response-1'); - res.setHeader('x-azle-response-2', 'x-azle-response-2'); + res.setHeader('X-Azle-Response-Key-0', 'X-Azle-Response-Value-0'); + res.setHeader('X-Azle-Response-Key-1', 'X-Azle-Response-Value-1'); + res.setHeader('X-Azle-Response-Key-2', 'X-Azle-Response-Value-2'); res.json({ whoami: ic.caller().toString(), @@ -29,9 +29,9 @@ export default Server(() => { }); app.post('/body-uint8array', (req, res) => { - res.setHeader('x-azle-response-0', 'x-azle-response-0'); - res.setHeader('x-azle-response-1', 'x-azle-response-1'); - res.setHeader('x-azle-response-2', 'x-azle-response-2'); + res.setHeader('X-Azle-Response-Key-0', 'X-Azle-Response-Value-0'); + res.setHeader('X-Azle-Response-Key-1', 'X-Azle-Response-Value-1'); + res.setHeader('X-Azle-Response-Key-2', 'X-Azle-Response-Value-2'); res.json({ whoami: ic.caller().toString(), @@ -40,9 +40,9 @@ export default Server(() => { }); app.put('/body-string', (req, res) => { - res.setHeader('x-azle-response-0', 'x-azle-response-0'); - res.setHeader('x-azle-response-1', 'x-azle-response-1'); - res.setHeader('x-azle-response-2', 'x-azle-response-2'); + res.setHeader('X-Azle-Response-Key-0', 'X-Azle-Response-Value-0'); + res.setHeader('X-Azle-Response-Key-1', 'X-Azle-Response-Value-1'); + res.setHeader('X-Azle-Response-Key-2', 'X-Azle-Response-Value-2'); res.json({ whoami: ic.caller().toString(), @@ -51,9 +51,9 @@ export default Server(() => { }); app.patch('/body-array-buffer', (req, res) => { - res.setHeader('x-azle-response-0', 'x-azle-response-0'); - res.setHeader('x-azle-response-1', 'x-azle-response-1'); - res.setHeader('x-azle-response-2', 'x-azle-response-2'); + res.setHeader('X-Azle-Response-Key-0', 'X-Azle-Response-Value-0'); + res.setHeader('X-Azle-Response-Key-1', 'X-Azle-Response-Value-1'); + res.setHeader('X-Azle-Response-Key-2', 'X-Azle-Response-Value-2'); res.json({ whoami: ic.caller().toString(), @@ -62,9 +62,9 @@ export default Server(() => { }); app.delete('/body-blob', (req, res) => { - res.setHeader('x-azle-response-0', 'x-azle-response-0'); - res.setHeader('x-azle-response-1', 'x-azle-response-1'); - res.setHeader('x-azle-response-2', 'x-azle-response-2'); + res.setHeader('X-Azle-Response-Key-0', 'X-Azle-Response-Value-0'); + res.setHeader('X-Azle-Response-Key-1', 'X-Azle-Response-Value-1'); + res.setHeader('X-Azle-Response-Key-2', 'X-Azle-Response-Value-2'); res.json({ whoami: ic.caller().toString(), @@ -73,9 +73,9 @@ export default Server(() => { }); app.post('/body-data-view', (req, res) => { - res.setHeader('x-azle-response-0', 'x-azle-response-0'); - res.setHeader('x-azle-response-1', 'x-azle-response-1'); - res.setHeader('x-azle-response-2', 'x-azle-response-2'); + res.setHeader('X-Azle-Response-Key-0', 'X-Azle-Response-Value-0'); + res.setHeader('X-Azle-Response-Key-1', 'X-Azle-Response-Value-1'); + res.setHeader('X-Azle-Response-Key-2', 'X-Azle-Response-Value-2'); res.json({ whoami: ic.caller().toString(), @@ -84,9 +84,9 @@ export default Server(() => { }); app.get('/url-query-params-get', (req, res) => { - res.setHeader('x-azle-response-0', 'x-azle-response-0'); - res.setHeader('x-azle-response-1', 'x-azle-response-1'); - res.setHeader('x-azle-response-2', 'x-azle-response-2'); + res.setHeader('X-Azle-Response-Key-0', 'X-Azle-Response-Value-0'); + res.setHeader('X-Azle-Response-Key-1', 'X-Azle-Response-Value-1'); + res.setHeader('X-Azle-Response-Key-2', 'X-Azle-Response-Value-2'); res.json({ whoami: ic.caller().toString(), @@ -95,9 +95,9 @@ export default Server(() => { }); app.post('/url-query-params-post', (req, res) => { - res.setHeader('x-azle-response-0', 'x-azle-response-0'); - res.setHeader('x-azle-response-1', 'x-azle-response-1'); - res.setHeader('x-azle-response-2', 'x-azle-response-2'); + res.setHeader('X-Azle-Response-Key-0', 'X-Azle-Response-Value-0'); + res.setHeader('X-Azle-Response-Key-1', 'X-Azle-Response-Value-1'); + res.setHeader('X-Azle-Response-Key-2', 'X-Azle-Response-Value-2'); res.json({ whoami: ic.caller().toString(), @@ -105,23 +105,26 @@ export default Server(() => { }); }); - app.get('/not-authorized-get', (req, res) => { + app.get('/not-authorized-get', (_req, res) => { res.status(401).send('Not Authorized'); }); - app.post('/not-authorized-post', (req, res) => { + app.post('/not-authorized-post', (_req, res) => { res.status(401).send('Not Authorized'); }); - app.head('/head', (req, res) => { - res.setHeader('x-azle-response-0', 'x-azle-response-0'); - res.setHeader('x-azle-response-1', 'x-azle-response-1'); - res.setHeader('x-azle-response-2', 'x-azle-response-2'); + app.head('/head', (_req, res) => { + res.setHeader('X-Azle-Response-Key-0', 'X-Azle-Response-Value-0'); + res.setHeader('X-Azle-Response-Key-1', 'X-Azle-Response-Value-1'); + res.setHeader('X-Azle-Response-Key-2', 'X-Azle-Response-Value-2'); res.end(); }); - app.options('/options', (req, res) => { - res.setHeader('x-azle-response-options', 'x-azle-response-options'); + app.options('/options', (_req, res) => { + res.setHeader( + 'X-Azle-Response-Key-Options', + 'X-Azle-Response-Value-Options' + ); res.end(); }); diff --git a/examples/fetch_ic/src/frontend/index.ts b/examples/fetch_ic/src/frontend/index.ts index d03d661d7c..1ee632ac45 100644 --- a/examples/fetch_ic/src/frontend/index.ts +++ b/examples/fetch_ic/src/frontend/index.ts @@ -53,9 +53,9 @@ export class AzleApp extends LitElement { method: 'GET', headers: [ ['Authorization', toJwt(this.identity)], - ['x-azle-request-0', 'x-azle-request-0'], - ['x-azle-request-1', 'x-azle-request-1'], - ['x-azle-request-2', 'x-azle-request-2'] + ['X-Azle-Request-Key-0', 'X-Azle-Request-Value-0'], + ['X-Azle-Request-Key-1', 'X-Azle-Request-Value-1'], + ['X-Azle-Request-Key-2', 'X-Azle-Request-Value-2'] ] } ); @@ -63,12 +63,18 @@ export class AzleApp extends LitElement { if ( responseJson.whoami === this.identity.getPrincipal().toString() && - responseJson.value['x-azle-request-0'] === 'x-azle-request-0' && - responseJson.value['x-azle-request-1'] === 'x-azle-request-1' && - responseJson.value['x-azle-request-2'] === 'x-azle-request-2' && - response.headers.get('x-azle-response-0') === 'x-azle-response-0' && - response.headers.get('x-azle-response-1') === 'x-azle-response-1' && - response.headers.get('x-azle-response-2') === 'x-azle-response-2' + responseJson.value['X-Azle-Request-Key-0'.toLowerCase()] === + 'X-Azle-Request-Value-0' && + responseJson.value['X-Azle-Request-Key-1'.toLowerCase()] === + 'X-Azle-Request-Value-1' && + responseJson.value['X-Azle-Request-Key-2'.toLowerCase()] === + 'X-Azle-Request-Value-2' && + response.headers.get('X-Azle-Response-Key-0') === + 'X-Azle-Response-Value-0' && + response.headers.get('X-Azle-Response-Key-1') === + 'X-Azle-Response-Value-1' && + response.headers.get('X-Azle-Response-Key-2') === + 'X-Azle-Response-Value-2' ) { (window as any).headersArraySuccess = true; } @@ -85,9 +91,9 @@ export class AzleApp extends LitElement { method: 'GET', headers: { Authorization: toJwt(this.identity), - 'x-azle-request-0': 'x-azle-request-0', - 'x-azle-request-1': 'x-azle-request-1', - 'x-azle-request-2': 'x-azle-request-2' + 'X-Azle-Request-Key-0': 'X-Azle-Request-Value-0', + 'X-Azle-Request-Key-1': 'X-Azle-Request-Value-1', + 'X-Azle-Request-Key-2': 'X-Azle-Request-Value-2' } } ); @@ -95,12 +101,18 @@ export class AzleApp extends LitElement { if ( responseJson.whoami === this.identity.getPrincipal().toString() && - responseJson.value['x-azle-request-0'] === 'x-azle-request-0' && - responseJson.value['x-azle-request-1'] === 'x-azle-request-1' && - responseJson.value['x-azle-request-2'] === 'x-azle-request-2' && - response.headers.get('x-azle-response-0') === 'x-azle-response-0' && - response.headers.get('x-azle-response-1') === 'x-azle-response-1' && - response.headers.get('x-azle-response-2') === 'x-azle-response-2' + responseJson.value['X-Azle-Request-Key-0'.toLowerCase()] === + 'X-Azle-Request-Value-0' && + responseJson.value['X-Azle-Request-Key-1'.toLowerCase()] === + 'X-Azle-Request-Value-1' && + responseJson.value['X-Azle-Request-Key-2'.toLowerCase()] === + 'X-Azle-Request-Value-2' && + response.headers.get('X-Azle-Response-Key-0') === + 'X-Azle-Response-Value-0' && + response.headers.get('X-Azle-Response-Key-1') === + 'X-Azle-Response-Value-1' && + response.headers.get('X-Azle-Response-Key-2') === + 'X-Azle-Response-Value-2' ) { (window as any).headersObjectSuccess = true; } @@ -137,9 +149,12 @@ export class AzleApp extends LitElement { JSON.stringify({ value: 'body-uint8array' }) && - response.headers.get('x-azle-response-0') === 'x-azle-response-0' && - response.headers.get('x-azle-response-1') === 'x-azle-response-1' && - response.headers.get('x-azle-response-2') === 'x-azle-response-2' + response.headers.get('X-Azle-Response-Key-0') === + 'X-Azle-Response-Value-0' && + response.headers.get('X-Azle-Response-Key-1') === + 'X-Azle-Response-Value-1' && + response.headers.get('X-Azle-Response-Key-2') === + 'X-Azle-Response-Value-2' ) { (window as any).bodyUint8ArraySuccess = true; } @@ -171,9 +186,12 @@ export class AzleApp extends LitElement { JSON.stringify({ value: 'body-string' }) && - response.headers.get('x-azle-response-0') === 'x-azle-response-0' && - response.headers.get('x-azle-response-1') === 'x-azle-response-1' && - response.headers.get('x-azle-response-2') === 'x-azle-response-2' + response.headers.get('X-Azle-Response-Key-0') === + 'X-Azle-Response-Value-0' && + response.headers.get('X-Azle-Response-Key-1') === + 'X-Azle-Response-Value-1' && + response.headers.get('X-Azle-Response-Key-2') === + 'X-Azle-Response-Value-2' ) { (window as any).bodyStringSuccess = true; } @@ -210,9 +228,12 @@ export class AzleApp extends LitElement { JSON.stringify({ value: 'body-array-buffer' }) && - response.headers.get('x-azle-response-0') === 'x-azle-response-0' && - response.headers.get('x-azle-response-1') === 'x-azle-response-1' && - response.headers.get('x-azle-response-2') === 'x-azle-response-2' + response.headers.get('X-Azle-Response-Key-0') === + 'X-Azle-Response-Value-0' && + response.headers.get('X-Azle-Response-Key-1') === + 'X-Azle-Response-Value-1' && + response.headers.get('X-Azle-Response-Key-2') === + 'X-Azle-Response-Value-2' ) { (window as any).bodyArrayBufferSuccess = true; } @@ -251,9 +272,12 @@ export class AzleApp extends LitElement { JSON.stringify({ value: 'body-blob' }) && - response.headers.get('x-azle-response-0') === 'x-azle-response-0' && - response.headers.get('x-azle-response-1') === 'x-azle-response-1' && - response.headers.get('x-azle-response-2') === 'x-azle-response-2' + response.headers.get('X-Azle-Response-Key-0') === + 'X-Azle-Response-Value-0' && + response.headers.get('X-Azle-Response-Key-1') === + 'X-Azle-Response-Value-1' && + response.headers.get('X-Azle-Response-Key-2') === + 'X-Azle-Response-Value-2' ) { (window as any).bodyBlobSuccess = true; } @@ -292,9 +316,12 @@ export class AzleApp extends LitElement { JSON.stringify({ value: 'body-data-view' }) && - response.headers.get('x-azle-response-0') === 'x-azle-response-0' && - response.headers.get('x-azle-response-1') === 'x-azle-response-1' && - response.headers.get('x-azle-response-2') === 'x-azle-response-2' + response.headers.get('X-Azle-Response-Key-0') === + 'X-Azle-Response-Value-0' && + response.headers.get('X-Azle-Response-Key-1') === + 'X-Azle-Response-Value-1' && + response.headers.get('X-Azle-Response-Key-2') === + 'X-Azle-Response-Value-2' ) { (window as any).bodyDataViewSuccess = true; } @@ -318,9 +345,12 @@ export class AzleApp extends LitElement { if ( responseJson.whoami === this.identity.getPrincipal().toString() && responseJson.value.type === 'get' && - response.headers.get('x-azle-response-0') === 'x-azle-response-0' && - response.headers.get('x-azle-response-1') === 'x-azle-response-1' && - response.headers.get('x-azle-response-2') === 'x-azle-response-2' + response.headers.get('X-Azle-Response-Key-0') === + 'X-Azle-Response-Value-0' && + response.headers.get('X-Azle-Response-Key-1') === + 'X-Azle-Response-Value-1' && + response.headers.get('X-Azle-Response-Key-2') === + 'X-Azle-Response-Value-2' ) { (window as any).urlQueryParamsGetSuccess = true; } @@ -345,9 +375,12 @@ export class AzleApp extends LitElement { if ( responseJson.whoami === this.identity.getPrincipal().toString() && responseJson.value.type === 'post' && - response.headers.get('x-azle-response-0') === 'x-azle-response-0' && - response.headers.get('x-azle-response-1') === 'x-azle-response-1' && - response.headers.get('x-azle-response-2') === 'x-azle-response-2' + response.headers.get('X-Azle-Response-Key-0') === + 'X-Azle-Response-Value-0' && + response.headers.get('X-Azle-Response-Key-1') === + 'X-Azle-Response-Value-1' && + response.headers.get('X-Azle-Response-Key-2') === + 'X-Azle-Response-Value-2' ) { (window as any).urlQueryParamsPostSuccess = true; } @@ -390,9 +423,12 @@ export class AzleApp extends LitElement { ); if ( - response.headers.get('x-azle-response-0') === 'x-azle-response-0' && - response.headers.get('x-azle-response-1') === 'x-azle-response-1' && - response.headers.get('x-azle-response-2') === 'x-azle-response-2' + response.headers.get('X-Azle-Response-Key-0') === + 'X-Azle-Response-Value-0' && + response.headers.get('X-Azle-Response-Key-1') === + 'X-Azle-Response-Value-1' && + response.headers.get('X-Azle-Response-Key-2') === + 'X-Azle-Response-Value-2' ) { (window as any).headSuccess = true; } @@ -408,8 +444,8 @@ export class AzleApp extends LitElement { ); if ( - response.headers.get('x-azle-response-options') === - 'x-azle-response-options' + response.headers.get('X-Azle-Response-Key-Options') === + 'X-Azle-Response-Value-Options' ) { (window as any).optionsSuccess = true; } diff --git a/examples/fetch_ic/test/tests.ts b/examples/fetch_ic/test/tests.ts index 5bd4216a3e..c9a7ab958e 100644 --- a/examples/fetch_ic/test/tests.ts +++ b/examples/fetch_ic/test/tests.ts @@ -5,7 +5,6 @@ import * as dns from 'node:dns'; dns.setDefaultResultOrder('ipv4first'); -import { Principal } from '@dfinity/principal'; // TODO get rid of this dependency if we installed it import { Test, getCanisterOrigin } from 'azle/test'; import puppeteer, { Browser, Page } from 'puppeteer'; @@ -28,7 +27,7 @@ export function getTests(canisterName: string): Test[] { page.on('console', (message) => { for (const arg of message.args()) { - console.log(`Puppetteer log: ${arg}`); + console.info(`Puppetteer log: ${arg}`); } }); @@ -283,7 +282,7 @@ export function getTests(canisterName: string): Test[] { ]; } -function iiLogin(page: Page) { +function iiLogin(page: Page): Promise { return new Promise((resolve) => { page.once('popup', async (iiPage) => { if (iiPage === null) { diff --git a/examples/hybrid_canister/dfx.json b/examples/hybrid_canister/dfx.json index 9db0597763..83eaf50fe2 100644 --- a/examples/hybrid_canister/dfx.json +++ b/examples/hybrid_canister/dfx.json @@ -66,6 +66,29 @@ "output": "test/dfx_generated/canister", "node_compatibility": true } + }, + "canister_init_and_post_upgrade": { + "type": "custom", + "init_arg": "(\"http-query-canister-init-and-post-upgrade\", \"http-update-canister-init-and-post-upgrade\", \"candidQueryCanisterInitAndPostUpgrade\", \"candidUpdateCanisterInitAndPostUpgrade\")", + "main": "src/canister_init_and_post_upgrade.ts", + "candid": "src/canister_init_and_post_upgrade.did", + "build": "npx azle canister_init_and_post_upgrade", + "wasm": ".azle/canister_init_and_post_upgrade/canister_init_and_post_upgrade.wasm", + "gzip": true, + "metadata": [ + { + "name": "candid:service", + "path": "src/canister_init_and_post_upgrade.did" + }, + { + "name": "cdk:name", + "content": "azle" + } + ], + "declarations": { + "output": "test/dfx_generated/canister_init_and_post_upgrade", + "node_compatibility": true + } } } } diff --git a/examples/hybrid_canister/src/canister.ts b/examples/hybrid_canister/src/canister.ts index 1a65eb1961..5f6c049e54 100644 --- a/examples/hybrid_canister/src/canister.ts +++ b/examples/hybrid_canister/src/canister.ts @@ -14,11 +14,11 @@ export default Canister({ function serverCallback() { const app = express(); - app.get('/http-query', (req, res) => { + app.get('/http-query', (_req, res) => { res.send('http-query-canister'); }); - app.post('/http-update', (req, res) => { + app.post('/http-update', (_req, res) => { res.send('http-update-canister'); }); return app.listen(); diff --git a/examples/hybrid_canister/src/canister_init_and_post_upgrade.did b/examples/hybrid_canister/src/canister_init_and_post_upgrade.did new file mode 100644 index 0000000000..6293f81261 --- /dev/null +++ b/examples/hybrid_canister/src/canister_init_and_post_upgrade.did @@ -0,0 +1,6 @@ +service: (text, text, text, text) -> { + candidQuery: () -> (text) query; + candidUpdate: () -> (text); + http_request: (record {url:text; method:text; body:vec nat8; headers:vec record {text; text}; certificate_version:opt nat16}) -> (record {body:vec nat8; headers:vec record {text; text}; upgrade:opt bool; streaming_strategy:opt variant {Callback:record {token:vec nat8; callback:func (vec nat8) -> (opt record {token:opt vec nat8; body:vec nat8}) query}}; status_code:nat16}) query; + http_request_update: (record {url:text; method:text; body:vec nat8; headers:vec record {text; text}}) -> (record {body:vec nat8; headers:vec record {text; text}; upgrade:opt bool; streaming_strategy:opt variant {Callback:record {token:vec nat8; callback:func (vec nat8) -> (opt record {token:opt vec nat8; body:vec nat8}) query}}; status_code:nat16}); +} diff --git a/examples/hybrid_canister/src/canister_init_and_post_upgrade.ts b/examples/hybrid_canister/src/canister_init_and_post_upgrade.ts new file mode 100644 index 0000000000..4daf8bf52a --- /dev/null +++ b/examples/hybrid_canister/src/canister_init_and_post_upgrade.ts @@ -0,0 +1,61 @@ +import { + Canister, + init, + postUpgrade, + query, + serverCanisterMethods, + setNodeServer, + text, + update +} from 'azle'; +import express from 'express'; + +let httpQueryText = ''; +let httpUpdateText = ''; + +let candidQueryText = ''; +let candidUpdateText = ''; + +export default Canister({ + ...serverCanisterMethods(serverCallback), + init: init([text, text, text, text], (param0, param1, param2, param3) => { + httpQueryText = param0 + '-init'; + httpUpdateText = param1 + '-init'; + + candidQueryText = param2 + '-init'; + candidUpdateText = param3 + '-init'; + + setNodeServer(serverCallback()); + }), + postUpgrade: postUpgrade( + [text, text, text, text], + (param0, param1, param2, param3) => { + httpQueryText = param0 + '-postUpgrade'; + httpUpdateText = param1 + '-postUpgrade'; + + candidQueryText = param2 + '-postUpgrade'; + candidUpdateText = param3 + '-postUpgrade'; + + setNodeServer(serverCallback()); + } + ), + candidQuery: query([], text, () => { + return candidQueryText; + }), + candidUpdate: update([], text, () => { + return candidUpdateText; + }) +}); + +function serverCallback() { + const app = express(); + + app.get('/http-query', (_req, res) => { + res.send(httpQueryText); + }); + + app.post('/http-update', (_req, res) => { + res.send(httpUpdateText); + }); + return app.listen(); +} diff --git a/examples/hybrid_canister/src/server_init_and_post_upgrade.ts b/examples/hybrid_canister/src/server_init_and_post_upgrade.ts index addc1a1d89..a9a77fb1e2 100644 --- a/examples/hybrid_canister/src/server_init_and_post_upgrade.ts +++ b/examples/hybrid_canister/src/server_init_and_post_upgrade.ts @@ -17,22 +17,22 @@ let candidUpdateText = ''; export default Server(serverCallback, { init: init([text, text, text, text], (param0, param1, param2, param3) => { - httpQueryText = param0; - httpUpdateText = param1; + httpQueryText = param0 + '-init'; + httpUpdateText = param1 + '-init'; - candidQueryText = param2; - candidUpdateText = param3; + candidQueryText = param2 + '-init'; + candidUpdateText = param3 + '-init'; setNodeServer(serverCallback()); }), postUpgrade: postUpgrade( [text, text, text, text], (param0, param1, param2, param3) => { - httpQueryText = param0; - httpUpdateText = param1; + httpQueryText = param0 + '-postUpgrade'; + httpUpdateText = param1 + '-postUpgrade'; - candidQueryText = param2; - candidUpdateText = param3; + candidQueryText = param2 + '-postUpgrade'; + candidUpdateText = param3 + '-postUpgrade'; setNodeServer(serverCallback()); } diff --git a/examples/hybrid_canister/test/pretest.ts b/examples/hybrid_canister/test/pretest.ts index 69a823cfaa..d9665ad58b 100644 --- a/examples/hybrid_canister/test/pretest.ts +++ b/examples/hybrid_canister/test/pretest.ts @@ -1,8 +1,6 @@ import { execSync } from 'child_process'; async function pretest() { - await new Promise((resolve) => setTimeout(resolve, 5000)); - execSync(`dfx canister uninstall-code server || true`, { stdio: 'inherit' }); @@ -18,6 +16,13 @@ async function pretest() { stdio: 'inherit' }); + execSync( + `dfx canister uninstall-code canister_init_and_post_upgrade || true`, + { + stdio: 'inherit' + } + ); + execSync(`dfx deploy`, { stdio: 'inherit' }); diff --git a/examples/hybrid_canister/test/tests.ts b/examples/hybrid_canister/test/tests.ts deleted file mode 100644 index eaf238a63d..0000000000 --- a/examples/hybrid_canister/test/tests.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as dns from 'node:dns'; -dns.setDefaultResultOrder('ipv4first'); - -import { getCanisterId, Test } from 'azle/test'; -import { createActor as createActorServer } from './dfx_generated/server'; -import { createActor as createActorServerInitAndPostUpgrade } from './dfx_generated/server_init_and_post_upgrade'; -import { createActor as createActorCanister } from './dfx_generated/canister'; - -export function getTests(): Test[] { - const canisterIdServer = getCanisterId('server'); - const canisterIdServerInitAndPostUpgrade = getCanisterId( - 'server_init_and_post_upgrade' - ); - const canisterIdCanister = getCanisterId('canister'); - - const originServer = `http://${canisterIdServer}.localhost:8000`; - const originServerInitAndPostUpgrade = `http://${canisterIdServerInitAndPostUpgrade}.localhost:8000`; - const originCanister = `http://${canisterIdCanister}.localhost:8000`; - - const actorServer = createActorServer(canisterIdServer, { - agentOptions: { - host: 'http://127.0.0.1:8000' - } - }); - const actorServerInitAndPostUpgrade = createActorServerInitAndPostUpgrade( - canisterIdServerInitAndPostUpgrade, - { - agentOptions: { - host: 'http://127.0.0.1:8000' - } - } - ); - const actorCanister = createActorCanister(canisterIdCanister, { - agentOptions: { - host: 'http://127.0.0.1:8000' - } - }); - - return [ - { - name: 'server', - test: async () => { - try { - const httpQueryResponse = await fetch( - `${originServer}/http-query` - ); - const httpQueryResponseText = - await httpQueryResponse.text(); - - const httpUpdateResponse = await fetch( - `${originServer}/http-update`, - { - method: 'POST' - } - ); - const httpUpdateResponseText = - await httpUpdateResponse.text(); - - const candidQueryText = await actorServer.candidQuery(); - const candidUpdateText = await actorServer.candidUpdate(); - - return { - Ok: - httpQueryResponseText === 'http-query-server' && - httpUpdateResponseText === 'http-update-server' && - candidQueryText === 'candidQueryServer' && - candidUpdateText === 'candidUpdateServer' - }; - } catch (error: any) { - return { - Err: error - }; - } - } - }, - { - name: 'server_init_and_post_upgrade', - test: async () => { - try { - const httpQueryResponse = await fetch( - `${originServerInitAndPostUpgrade}/http-query` - ); - const httpQueryResponseText = - await httpQueryResponse.text(); - - const httpUpdateResponse = await fetch( - `${originServerInitAndPostUpgrade}/http-update`, - { - method: 'POST' - } - ); - const httpUpdateResponseText = - await httpUpdateResponse.text(); - - const candidQueryText = - await actorServerInitAndPostUpgrade.candidQuery(); - const candidUpdateText = - await actorServerInitAndPostUpgrade.candidUpdate(); - - return { - Ok: - httpQueryResponseText === - 'http-query-server-init-and-post-upgrade' && - httpUpdateResponseText === - 'http-update-server-init-and-post-upgrade' && - candidQueryText === - 'candidQueryServerInitAndPostUpgrade' && - candidUpdateText === - 'candidUpdateServerInitAndPostUpgrade' - }; - } catch (error: any) { - return { - Err: error - }; - } - } - }, - { - name: 'canister', - test: async () => { - try { - const httpQueryResponse = await fetch( - `${originCanister}/http-query` - ); - const httpQueryResponseText = - await httpQueryResponse.text(); - - const httpUpdateResponse = await fetch( - `${originCanister}/http-update`, - { - method: 'POST' - } - ); - const httpUpdateResponseText = - await httpUpdateResponse.text(); - - const candidQueryText = await actorCanister.candidQuery(); - const candidUpdateText = await actorCanister.candidUpdate(); - - return { - Ok: - httpQueryResponseText === 'http-query-canister' && - httpUpdateResponseText === 'http-update-canister' && - candidQueryText === 'candidQueryCanister' && - candidUpdateText === 'candidUpdateCanister' - }; - } catch (error: any) { - return { - Err: error - }; - } - } - } - ]; -} diff --git a/examples/hybrid_canister/test/tests/canister.ts b/examples/hybrid_canister/test/tests/canister.ts new file mode 100644 index 0000000000..5125fdcbdb --- /dev/null +++ b/examples/hybrid_canister/test/tests/canister.ts @@ -0,0 +1,52 @@ +import { getCanisterId, Test } from 'azle/test'; + +import { createActor } from '../dfx_generated/canister'; + +export function getTests(): Test[] { + const canisterId = getCanisterId('canister'); + const origin = `http://${canisterId}.localhost:8000`; + const actor = createActor(canisterId, { + agentOptions: { + host: 'http://127.0.0.1:8000' + } + }); + + return [ + { + name: 'canister', + test: async () => { + try { + const httpQueryResponse = await fetch( + `${origin}/http-query` + ); + const httpQueryResponseText = + await httpQueryResponse.text(); + + const httpUpdateResponse = await fetch( + `${origin}/http-update`, + { + method: 'POST' + } + ); + const httpUpdateResponseText = + await httpUpdateResponse.text(); + + const candidQueryText = await actor.candidQuery(); + const candidUpdateText = await actor.candidUpdate(); + + return { + Ok: + httpQueryResponseText === 'http-query-canister' && + httpUpdateResponseText === 'http-update-canister' && + candidQueryText === 'candidQueryCanister' && + candidUpdateText === 'candidUpdateCanister' + }; + } catch (error: any) { + return { + Err: error + }; + } + } + } + ]; +} diff --git a/examples/hybrid_canister/test/tests/canister_init_and_post_upgrade.ts b/examples/hybrid_canister/test/tests/canister_init_and_post_upgrade.ts new file mode 100644 index 0000000000..ed07d5e6a8 --- /dev/null +++ b/examples/hybrid_canister/test/tests/canister_init_and_post_upgrade.ts @@ -0,0 +1,108 @@ +import { getCanisterId, Test } from 'azle/test'; +import { execSync } from 'child_process'; + +import { createActor } from '../dfx_generated/canister_init_and_post_upgrade'; + +export function getTests(): Test[] { + const canisterId = getCanisterId('canister_init_and_post_upgrade'); + const origin = `http://${canisterId}.localhost:8000`; + const actor = createActor(canisterId, { + agentOptions: { + host: 'http://127.0.0.1:8000' + } + }); + + return [ + { + name: 'canister_init_and_post_upgrade init', + test: async () => { + try { + const httpQueryResponse = await fetch( + `${origin}/http-query` + ); + const httpQueryResponseText = + await httpQueryResponse.text(); + + const httpUpdateResponse = await fetch( + `${origin}/http-update`, + { + method: 'POST' + } + ); + const httpUpdateResponseText = + await httpUpdateResponse.text(); + + const candidQueryText = await actor.candidQuery(); + const candidUpdateText = await actor.candidUpdate(); + + return { + Ok: + httpQueryResponseText === + 'http-query-canister-init-and-post-upgrade-init' && + httpUpdateResponseText === + 'http-update-canister-init-and-post-upgrade-init' && + candidQueryText === + 'candidQueryCanisterInitAndPostUpgrade-init' && + candidUpdateText === + 'candidUpdateCanisterInitAndPostUpgrade-init' + }; + } catch (error: any) { + return { + Err: error + }; + } + } + }, + { + name: 'deploy canister_init_and_post_upgrade', + prep: async () => { + execSync( + `dfx deploy --upgrade-unchanged canister_init_and_post_upgrade`, + { + stdio: 'inherit' + } + ); + } + }, + { + name: 'canister_init_and_post_upgrade postUpgrade', + test: async () => { + try { + const httpQueryResponse = await fetch( + `${origin}/http-query` + ); + const httpQueryResponseText = + await httpQueryResponse.text(); + + const httpUpdateResponse = await fetch( + `${origin}/http-update`, + { + method: 'POST' + } + ); + const httpUpdateResponseText = + await httpUpdateResponse.text(); + + const candidQueryText = await actor.candidQuery(); + const candidUpdateText = await actor.candidUpdate(); + + return { + Ok: + httpQueryResponseText === + 'http-query-canister-init-and-post-upgrade-postUpgrade' && + httpUpdateResponseText === + 'http-update-canister-init-and-post-upgrade-postUpgrade' && + candidQueryText === + 'candidQueryCanisterInitAndPostUpgrade-postUpgrade' && + candidUpdateText === + 'candidUpdateCanisterInitAndPostUpgrade-postUpgrade' + }; + } catch (error: any) { + return { + Err: error + }; + } + } + } + ]; +} diff --git a/examples/hybrid_canister/test/tests/index.ts b/examples/hybrid_canister/test/tests/index.ts new file mode 100644 index 0000000000..d3b57df4cb --- /dev/null +++ b/examples/hybrid_canister/test/tests/index.ts @@ -0,0 +1,24 @@ +import * as dns from 'node:dns'; +dns.setDefaultResultOrder('ipv4first'); + +import { Test } from 'azle/test'; + +import { getTests as getTestsCanisterInitAndPostUpgrade } from './canister_init_and_post_upgrade'; +import { getTests as getTestsCanister } from './canister'; +import { getTests as getTestsServerInitAndPostUpgrade } from './server_init_and_post_upgrade'; +import { getTests as getTestsServer } from './server'; + +export function getTests(): Test[] { + const canisterInitAndPostUpgradeTests = + getTestsCanisterInitAndPostUpgrade(); + const canisterTests = getTestsCanister(); + const serverInitAndPostUpgradeTests = getTestsServerInitAndPostUpgrade(); + const serverTests = getTestsServer(); + + return [ + ...canisterInitAndPostUpgradeTests, + ...canisterTests, + ...serverInitAndPostUpgradeTests, + ...serverTests + ]; +} diff --git a/examples/hybrid_canister/test/tests/server.ts b/examples/hybrid_canister/test/tests/server.ts new file mode 100644 index 0000000000..2eae67e909 --- /dev/null +++ b/examples/hybrid_canister/test/tests/server.ts @@ -0,0 +1,52 @@ +import { getCanisterId, Test } from 'azle/test'; + +import { createActor } from '../dfx_generated/server'; + +export function getTests(): Test[] { + const canisterId = getCanisterId('server'); + const origin = `http://${canisterId}.localhost:8000`; + const actor = createActor(canisterId, { + agentOptions: { + host: 'http://127.0.0.1:8000' + } + }); + + return [ + { + name: 'server', + test: async () => { + try { + const httpQueryResponse = await fetch( + `${origin}/http-query` + ); + const httpQueryResponseText = + await httpQueryResponse.text(); + + const httpUpdateResponse = await fetch( + `${origin}/http-update`, + { + method: 'POST' + } + ); + const httpUpdateResponseText = + await httpUpdateResponse.text(); + + const candidQueryText = await actor.candidQuery(); + const candidUpdateText = await actor.candidUpdate(); + + return { + Ok: + httpQueryResponseText === 'http-query-server' && + httpUpdateResponseText === 'http-update-server' && + candidQueryText === 'candidQueryServer' && + candidUpdateText === 'candidUpdateServer' + }; + } catch (error: any) { + return { + Err: error + }; + } + } + } + ]; +} diff --git a/examples/hybrid_canister/test/tests/server_init_and_post_upgrade.ts b/examples/hybrid_canister/test/tests/server_init_and_post_upgrade.ts new file mode 100644 index 0000000000..8ad2565f73 --- /dev/null +++ b/examples/hybrid_canister/test/tests/server_init_and_post_upgrade.ts @@ -0,0 +1,108 @@ +import { getCanisterId, Test } from 'azle/test'; +import { execSync } from 'child_process'; + +import { createActor } from '../dfx_generated/server_init_and_post_upgrade'; + +export function getTests(): Test[] { + const canisterId = getCanisterId('server_init_and_post_upgrade'); + const origin = `http://${canisterId}.localhost:8000`; + const actor = createActor(canisterId, { + agentOptions: { + host: 'http://127.0.0.1:8000' + } + }); + + return [ + { + name: 'server_init_and_post_upgrade init', + test: async () => { + try { + const httpQueryResponse = await fetch( + `${origin}/http-query` + ); + const httpQueryResponseText = + await httpQueryResponse.text(); + + const httpUpdateResponse = await fetch( + `${origin}/http-update`, + { + method: 'POST' + } + ); + const httpUpdateResponseText = + await httpUpdateResponse.text(); + + const candidQueryText = await actor.candidQuery(); + const candidUpdateText = await actor.candidUpdate(); + + return { + Ok: + httpQueryResponseText === + 'http-query-server-init-and-post-upgrade-init' && + httpUpdateResponseText === + 'http-update-server-init-and-post-upgrade-init' && + candidQueryText === + 'candidQueryServerInitAndPostUpgrade-init' && + candidUpdateText === + 'candidUpdateServerInitAndPostUpgrade-init' + }; + } catch (error: any) { + return { + Err: error + }; + } + } + }, + { + name: 'deploy server_init_and_post_upgrade', + prep: async () => { + execSync( + `dfx deploy --upgrade-unchanged server_init_and_post_upgrade`, + { + stdio: 'inherit' + } + ); + } + }, + { + name: 'server_init_and_post_upgrade postUpgrade', + test: async () => { + try { + const httpQueryResponse = await fetch( + `${origin}/http-query` + ); + const httpQueryResponseText = + await httpQueryResponse.text(); + + const httpUpdateResponse = await fetch( + `${origin}/http-update`, + { + method: 'POST' + } + ); + const httpUpdateResponseText = + await httpUpdateResponse.text(); + + const candidQueryText = await actor.candidQuery(); + const candidUpdateText = await actor.candidUpdate(); + + return { + Ok: + httpQueryResponseText === + 'http-query-server-init-and-post-upgrade-postUpgrade' && + httpUpdateResponseText === + 'http-update-server-init-and-post-upgrade-postUpgrade' && + candidQueryText === + 'candidQueryServerInitAndPostUpgrade-postUpgrade' && + candidUpdateText === + 'candidUpdateServerInitAndPostUpgrade-postUpgrade' + }; + } catch (error: any) { + return { + Err: error + }; + } + } + } + ]; +} diff --git a/examples/tfjs/src/api.ts b/examples/tfjs/src/api.ts index ad1b6378aa..37596215fc 100644 --- a/examples/tfjs/src/api.ts +++ b/examples/tfjs/src/api.ts @@ -20,9 +20,7 @@ export default Server(async () => { }) as any }; - const model = await tf.loadLayersModel('file://spam/model.json', { - fetchFunc: fetch - }); + const model = await tf.loadLayersModel('file://spam/model.json'); app.get('/prediction', async (_req, res) => { // TODO Tokenization and prediction for this specific model have not yet been figured out diff --git a/http_client/fetch_ic/call.ts b/http_client/fetch_ic/call.ts index 3ff276e3d6..791b530b11 100644 --- a/http_client/fetch_ic/call.ts +++ b/http_client/fetch_ic/call.ts @@ -1,12 +1,22 @@ +// TODO CONNECT and TRACE are not currently supported as we believe ICP does not support them in any way + import { createActor } from './actor'; -export type CallResult = Awaited>; +export type CallResult = CallHttpRequestResult | CallHttpRequestUpdateResult; + +export type CallHttpRequestResult = Awaited< + ReturnType>['http_request']> +>; + +export type CallHttpRequestUpdateResult = Awaited< + ReturnType>['http_request_update']> +>; export async function call( input: RequestInfo | URL, init: RequestInit | undefined, actor: Awaited> -) { +): Promise { const urlString = getUrlString(input); const url = new URL(urlString); const urlAndQueryParams = `${url.pathname}${url.search}`; @@ -158,7 +168,8 @@ function shouldCallHttpRequest( init.method === 'HEAD' || init.method === 'OPTIONS' || headers.find( - ([key, value]) => key === 'x-ic-force-query' && value === 'true' + ([key, value]) => + key.toLowerCase() === 'x-ic-force-query' && value === 'true' ) !== undefined ); } @@ -175,7 +186,8 @@ function shouldCallHttpRequestUpdate( init.method === 'DELETE' || headers.find( ([key, value]) => - key === 'x-ic-force-update' && value === 'true' + key.toLowerCase() === 'x-ic-force-update' && + value === 'true' ) !== undefined) ); } @@ -186,9 +198,13 @@ async function callHttpRequest( urlAndQueryParams: string, headers: [string, string][], body: Uint8Array -) { +): Promise { + const method = init?.method ?? 'GET'; + + checkGetHeadBody(method, body); + return await actor.http_request({ - method: init?.method ?? 'GET', + method, url: urlAndQueryParams, headers, body, @@ -202,9 +218,13 @@ async function callHttpRequestUpdate( urlAndQueryParams: string, headers: [string, string][], body: Uint8Array -) { +): Promise { + const method = init?.method ?? 'GET'; + + checkGetHeadBody(method, body); + return await actor.http_request_update({ - method: init?.method ?? 'GET', + method, url: urlAndQueryParams, headers: [ ...headers, @@ -218,3 +238,14 @@ async function callHttpRequestUpdate( body }); } + +function checkGetHeadBody(method: string, body: Uint8Array) { + if ( + (method.toLowerCase() === 'get' || method.toLowerCase() === 'head') && + body.length !== 0 + ) { + throw new Error( + `fetchIc: Request with GET/HEAD method cannot have body.` + ); + } +} diff --git a/http_client/fetch_ic/host.ts b/http_client/fetch_ic/host.ts index e83125ab4b..aea815f506 100644 --- a/http_client/fetch_ic/host.ts +++ b/http_client/fetch_ic/host.ts @@ -4,6 +4,10 @@ export function getHost(input: RequestInfo | URL): string { const runningLocally = hostString.includes(`localhost:`) || hostString.includes(`127.0.0.1:`); + // icp-api.io is the API gateway hostname: https://internetcomputer.org/docs/current/references/http-gateway-protocol-spec#api-gateway-resolution + // A production application may have a custom domain + // thus it is safest to use icp-api.io instead of the hostString when on mainnet + // I am also not sure if the hostString on mainnet e.g. https://canisterId.icp0.io can be used const host = runningLocally === true ? hostString : 'http://icp-api.io'; return host; diff --git a/http_client/fetch_ic/identity.ts b/http_client/fetch_ic/identity.ts index c5767b0247..4be537a60a 100644 --- a/http_client/fetch_ic/identity.ts +++ b/http_client/fetch_ic/identity.ts @@ -25,9 +25,8 @@ export function getIdentity( return authorizationHeaderValue; } -function getAuthorizationHeaderValue( - headers: RequestInit['headers'] -): string | undefined | null | Identity { +// The return type is explicitly any +function getAuthorizationHeaderValue(headers: RequestInit['headers']): any { if (Array.isArray(headers)) { return headers.reduce( (acc: string | undefined | Identity, [key, value]) => { diff --git a/src/lib/server.ts b/src/lib/server.ts index f5c4bbce35..30df0c5747 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -171,20 +171,7 @@ export async function httpHandler( throw new Error(`The server was not initialized`); } - if ( - query === true && - httpRequest.headers.find( - ([key, value]) => key === 'x-ic-force-query' && value === 'true' - ) === undefined && - (httpRequest.method === 'POST' || - httpRequest.method === 'PUT' || - httpRequest.method === 'PATCH' || - httpRequest.method === 'DELETE' || - httpRequest.headers.find( - ([key, value]) => - key === 'x-ic-force-update' && value === 'true' - ) !== undefined) - ) { + if (shouldUpgrade(httpRequest, query)) { ic.reply( { status_code: 204, @@ -320,6 +307,44 @@ export async function httpHandler( nodeServer.emit('request', req, res); } +function shouldUpgrade( + httpRequest: HttpRequest | HttpUpdateRequest, + query: boolean +): boolean { + const forceQueryHeaderExists = forceHeaderExists( + 'X-Ic-Force-Query', + httpRequest.headers + ); + + const forceUpdateHeaderExists = forceHeaderExists( + 'X-Ic-Force-Update', + httpRequest.headers + ); + + return ( + query === true && + !forceQueryHeaderExists && + (httpRequest.method === 'POST' || + httpRequest.method === 'PUT' || + httpRequest.method === 'PATCH' || + httpRequest.method === 'DELETE' || + forceUpdateHeaderExists) + ); +} + +function forceHeaderExists( + headerName: string, + headers: [string, string][] +): boolean { + return ( + headers.find( + ([key, value]) => + key.toLowerCase() === headerName.toLowerCase() && + value === 'true' + ) !== undefined + ); +} + // TODO I think this is correct but it is expensive // TODO we really just want the icx-proxy or boundary node to not // TODO automatically apply the Content-Length header